请访问我的独立博客地址:https://imsense.site/2017/06/js-decorator/

装饰器的流行应该感谢在Angular 2+中使用,在Angular中,装饰器因TypeScript能使用。但是在JavaScript中,还处于提议阶段。本文将介绍装饰器是什么,及装饰器如何让代码更加简洁和容易理解。

什么是装饰器

装饰器是用一个代码包装另一个代码的简单方式。

这个概念与之前所听过的函数复合和高阶组件相似。

这已经用于很多情况,就是简单的将一个函数包装成另一个函数:

function doSomething(name) {
console.log('Hello, ' + name);
} function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
} const wrapped = loggingDecorator(doSomething);

上个例子产生新函数wrapped,此函数与doSomething做同样事情,但是他们不同在于在包装函数之前和之后输出一些语句。

doSomething('Graham');
// Hello, Graham
wrapped('Graham');
// Starting
// Hello, Graham
// Finished

如何使用JavaScript装饰器

JavaScript中装饰器使用特殊的语法,使用@作为标识符,且放置在被装饰代码之前。

注意:现在装饰器还处于提议阶段,意味着还有可以变化之处

可以放置许多装饰器在同样代码之前,然后解释器会按照顺序相应执行

@log()
@immutable()
class Example {
@time('demo')
doSomething() { }
}

上例中定义了一个类,采用了三个装饰器:两个用于类本身,一个用于类的属性:

  • @log能记录所有所有访问类
  • @immutable让类不可变-也许新实例调用了Object.freeze
  • @time会记录一个方法从执行到输出一个独特标签

现在,虽然现在浏览器或Node还没支持。但是如果使用Babel,能使用 transform-decorators-legacy插件使用装饰器。

插件中使用legacy是因为Babel 5支持处理装饰器,但是它也许会跟最终的标准有区别,所以才使用legacy这个词。

为什么使用装饰器

函数复合在JavaScript已经成为可能,但是它相当困难或不可能用于另一个代码(如类或类属性)。

装饰器提议可以用于类或属性,未来JavaScript版本可能会增加用于其他地方。

装饰器也考虑到采用较为简洁的语法。

不同类型的装饰器

现在,装饰器只支持类和类属性,这包含属性、方法、get函数和set函数

装饰器只会在程序第一次运行时执行一次,装饰的代码会被返回的值代替

类属性装饰器

属性装饰器适用于类的单独成员-无论是属性、方法、get函数或set函数。

装饰器函数调用三个参数:

  • target-被修饰的类
  • name-类成员的名字
  • descriptor-成员描述符。对象会将这个参数传给Object.defineProperty

@readonly是经典的例子:

function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}

上例会将成员描述符中的writable设为false

接着用于类中属性:

class Example {
a() {}
@readonly
b() {}
} const e = new Example();
e.a = 1;
e.b = 2;
// TypeError: Cannot assign to read only property 'b' of object '#<Example>'

但是我们可以做的更好,可以用别的形式代替装饰函数。例如,记录所有的输入和输出:

function log(target, name, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result: ${result}`);
return result;
} catch (e) {
console.log(`Error: ${e}`);
throw e;
}
}
}
return descriptor;
}

注意我们使用了扩展运算符,会自动将所有参数转为数组。

class Example {
@log
sum(a, b) {
return a + b;
}
} const e = new Example();
e.sum(1, 2);
// Arguments: 1,2
// Result: 3

可以让装饰器获取一些参数,例如重写log装饰器如下:

function log(name) {
return function decorator(t, n, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments for ${name}: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result from ${name}: ${result}`);
return result;
} catch (e) {
console.log(`Error from ${name}: ${e}`);
throw e;
}
}
}
return descriptor;
};
}

这与之前的log装饰器相同,只是利用了外部函数的name参数。

class Example {
@log('some tag')
sum(a, b) {
return a + b;
}
} const e = new Example();
e.sum(1, 2);
// Arguments for some tag: 1,2
// Result from some tag: 3

类装饰器

类装饰器用于整个类,装饰器函数的参数为被装饰的构造器函数。

注意只用于构造器函数,而不适用于类的每个实例。这就意味着如果想控制实例,就必须返回一个包装版本的构造器函数。

通常,类装饰器没什么用处,因为你所需要做的,同样可以用一个简单函数来处理。你所做的只需要在结束时返回一个新的构造函数来代替类的构造函数。

回到我们记录那个例子,编写一个记录构造函数参数:

function log(Class) {
return (...args) => {
console.log(args);
return new Class(...args);
};
}

这里接收一个类作为参数,返回新函数作为构造器。此函数打印出参数,返回这些参数构造的实例。

例如:

@log
class Example {
constructor(name, age) {
}
} const e = new Example('Graham', 34);
// [ 'Graham', 34 ]
console.log(e);
// Example {}

构造Example类时会输出提供的参数,构造值e也确实是Example的实例。

传递参数到类装饰器与类成员一样。

function log(name) {
return function decorator(Class) {
return (...args) => {
console.log(`Arguments for ${name}: args`);
return new Class(...args);
};
}
} @log('Demo')
class Example {
constructor(name, age) {}
} const e = new Example('Graham', 34);
// Arguments for Demo: args
console.log(e);
// Example {}

真实例子

Core decorators

Core decorators是一个库,提供了几个常见的修饰器,通过它可以更好地理解修饰器。

想理解此库,也可以去看看阮老师的关于此库的介绍

React

React广泛运用了高阶组件,这让React组件成为一个函数,并且能包含另一个组件。

使用装饰器是不错的替代法,例如,Redux库有一个connect函数,用于连接React组件和React store。

通常,是这么使用的:

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

然而,可以使用装饰器代替:

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

参考资料

JavaScript Decorators: What They Are and When to Use Them

阮老师ES6入门-修饰器

JavaScript 的装饰器:它们是什么及如何使用的更多相关文章

  1. JAVASCRIPT中装饰器是什么(装修)

    装饰器是什么? 解码器是将另一段代码包装在一个代码中的简单方法. 这个概念类似于你以前听说过的功能成分和高阶成分. 这在许多情况下都被使用过,也就是说,成都装修公司简单地将一个函数包装到另一个函数中: ...

  2. 从ES6重新认识JavaScript设计模式: 装饰器模式

    1 什么是装饰器模式 向一个现有的对象添加新的功能,同时又不改变其结构的设计模式被称为装饰器模式(Decorator Pattern),它是作为现有的类的一个包装(Wrapper). 可以将装饰器理解 ...

  3. JavaScript设计模式—装饰器模式

    装饰器模式介绍 为对象添加新的功能,不改变其原有的结构和功能,原有的功能还是可以使用,跟适配器模式不一样,适配器模式原有的已经不能使用了,装饰器示例比如手机壳 UML类图和代码示例 Circle示原来 ...

  4. JavaScript学习笔记(四十四) 装饰器

    装饰器模式(Decorator) 在装饰器模式中,可以在运行时给一个对象动态的添加额外的功能.当和静态类打交道的时候(static classes),这可能是一个挑战.但在JavaScript中,对象 ...

  5. Javascript 装饰器极速指南

    pablo.png Decorators 是ES7中添加的JavaScript新特性.熟悉Typescript的同学应该更早的接触到这个特性,TypeScript早些时候已经支持Decorators的 ...

  6. javascript装饰器模式

    装饰器模式 什么是装饰器 原名decorator 被翻译为装饰器 可以理解为装饰 修饰 包装等意 现实中的作用 一间房子通过装饰可以变得更华丽,功能更多 类似一部手机可以单独使用 但是很多人都愿意家个 ...

  7. Javascript装饰器的妙用

    最近新开了一个Node项目,采用TypeScript来开发,在数据库及路由管理方面用了不少的装饰器,发觉这的确是一个好东西.装饰器是一个还处于草案中的特性,目前木有直接支持该语法的环境,但是可以通过 ...

  8. Python之路第一课Day4--随堂笔记(迭代生成装饰器)

    上节回顾: 1.集合 a.关系测试 b.去重 2.文件操作及编码 3.函数 4.局部变量和全局变量 上节回顾 本节课内容: 1.迭代器生成器 2.装饰器 3.json pickle数据序列化 4.软件 ...

  9. simple_tag,filte,分页以及cookie和装饰器

    自定义simple_tag 内置的方法 首先Django中包含了很多内置的方法: 这里通过lower实现 在views视图函数中写如下代码: def tp3(request): name= " ...

随机推荐

  1. Dubbo学习笔记7:Dubbo的集群容错与负载均衡策略

    Dubbo的集群容错策略 正常情况下,当我们进行系统设计时候,不仅要考虑正常逻辑下代码该如何走,还要考虑异常情况下代码逻辑应该怎么走.当服务消费方调用服务提供方的服务出现错误时候,Dubbo提供了多种 ...

  2. Linux命令(五)免密码远程登录和配置别名

    1. ssh-keygen 2. ssh-copy-id -p port user@remote .ssh中建立并编辑config文件 原来需要 ssh -p ubuntu@xxx.xxx.xxx 现 ...

  3. Ubuntu django+nginx 搭建python web服务器文件日志

    uwsgi 配置文件 [uwsgi] http-socket = 127.0.0.1:8080 # 项目目录 chdir=/home/ubuntu/mkweb # 指定项目的application m ...

  4. 为什么今天的L4无人驾驶无法到达终局(转)

    本文来自于公众号驭势未来,是驭势科技的微信公众平台,本博客收录的这篇文章版权作者吴甘沙,博客中仅对部分内容进行编辑. 作者:吴甘沙 原文链接:here 声明:文中的观点只代表版权作者的观点,本转载不涉 ...

  5. 第8月第15天 app支持后台播放

    1. AVAudioSession *audioSession = [AVAudioSession sharedInstance]; //默认情况下扬声器播放 [audioSession setCat ...

  6. Windows::Docker::Ubuntu 做 SLAM

    如题,这是一件很蛋疼的事情. 为了完成这一件事情,需要达成目标: Ubuntu GUI 必须要能够显示. Ubuntu 可以链接 USB Camera. 目标一 目标1很容易达成. 在 Win10 中 ...

  7. ecshop 2.7.x 批量测试

    下面为测试是否存在漏洞的脚本: sub MAIN($url) { use HTTP::UserAgent; my $r = HTTP::Request.new(); $r.uri: $url~'/us ...

  8. Linux Core Dump【转】

    转自:http://www.cnblogs.com/hazir/p/linxu_core_dump.html 当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中 ...

  9. 解决windows10 里vs2017 直接开始执行提示“此任务要求应用程序有提升的权限”1.

    1.打开vs的安装路径,我的是 C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv\ ,找到  VSLauncher.exe 右击该文 ...

  10. ZJOI2019爆蛋记

    Day-2 玩了一个下午,逛了填海校园,晚上吃肯德基 Day-1 上午听lyx巨佬讲课,讲到一半发现,越听越听不懂... 于是打开电脑开始刷知乎 下午听kcz孔爷讲课,emmmm电脑被我玩没电了... ...