JavaScript的装饰器:它们是什么及如何使用
2017-06-13 20:37
169 查看
装饰器的流行应该感谢在Angular 2+中使用,在Angular中,装饰器因TypeScript能使用。但是在JavaScript中,还处于提议阶段。本文将介绍装饰器是什么,及装饰器如何让代码更加简洁和容易理解。
这个概念与之前所听过的函数复合和高阶组件相似。
这已经用于很多情况,就是简单的将一个函数包装成另一个函数:
上个例子产生新函数
注意:现在装饰器还处于提议阶段,意味着还有可以变化之处
可以放置许多装饰器在同样代码之前,然后解释器会按照顺序相应执行
上例中定义了一个类,采用了三个装饰器:两个用于类本身,一个用于类的属性:
-
-
-
现在,虽然现在浏览器或Node还没支持。但是如果使用Babel,能使用 transform-decorators-legacy插件使用装饰器。
插件中使用legacy是因为Babel 5支持处理装饰器,但是它也许会跟最终的标准有区别,所以才使用legacy这个词。
装饰器提议可以用于类或属性,未来JavaScript版本可能会增加用于其他地方。
装饰器也考虑到采用较为简洁的语法。
装饰器只会在程序第一次运行时执行一次,装饰的代码会被返回的值代替
装饰器函数调用三个参数:
- target-被修饰的类
- name-类成员的名字
- descriptor-成员描述符。对象会将这个参数传给
上例会将成员描述符中的
接着用于类中属性:
但是我们可以做的更好,可以用别的形式代替装饰函数。例如,记录所有的输入和输出:
注意我们使用了扩展运算符,会自动将所有参数转为数组。
可以让装饰器获取一些参数,例如重写
这与之前的
注意只用于构造器函数,而不适用于类的每个实例。这就意味着如果想控制实例,就必须返回一个包装版本的构造器函数。
通常,类装饰器没什么用处,因为你所需要做的,同样可以用一个简单函数来处理。你所做的只需要在结束时返回一个新的构造函数来代替类的构造函数。
回到我们记录那个例子,编写一个记录构造函数参数:
这里接收一个类作为参数,返回新函数作为构造器。此函数打印出参数,返回这些参数构造的实例。
例如:
构造
传递参数到类装饰器与类成员一样。
想理解此库,也可以去看看阮老师的关于此库的介绍
使用装饰器是不错的替代法,例如,Redux库有一个
通常,是这么使用的:
然而,可以使用装饰器代替:
阮老师ES6入门-修饰器
欢迎订阅掘金专栏和知乎专栏
什么是装饰器
装饰器是用一个代码包装另一个代码的简单方式。这个概念与之前所听过的函数复合和高阶组件相似。
这已经用于很多情况,就是简单的将一个函数包装成另一个函数:
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 Function类是干什么用的,有什么用?使用Function如何创建函数?
- 深拷贝和浅拷贝的区别是什么?你会如何使用它们?
- AJAX是什么?如何在JavaScript中使用?
- 数据类型和抽象数据类型是如何定义的。二者有何相同和不同之处,抽象数据类型的主要特点是什么?使用抽象数据类型的主要好处是什么?
- 如何使用javascript限制文件上传大小
- 如何使用JavaScript进行可靠的继承调用
- 如何在ASP.NET中使用JavaScript脚本
- 如何使用JavaScript来写ASP程序
- asp.net中窗口相关操作(如何使用javascript) (转)
- COM编程入门(第一部分 什么是COM,如何使用COM)
- TrackBack是什么?如何使用TrackBack?
- 如何在ASP.NET中使用JavaScript脚本
- 新手来看:什么叫“使用Data Link 文件”?什么是UDL文件?如何创建?
- 绝对酷,如何解决asp.net中javascript脚本的问题(使用服务器控件执行客户端脚本)
- 如何在WebForm中使用javascript防止连打(双击)
- 如何在WebForm中使用javascript防止连打(双击)
- 如何在ASP.NET中使用JavaScript脚本
- 用户控件中使用了Javascript ,把2个控件放在页面上,那么页面上会有2个同名的javascript函数,如何处理
- [ChneChen的随笔][管理之道]什么才是我们需要的解决问题的方法(从如何限制公司电脑使用U盘解决之道谈起)
- 如何在WebForm中使用javascript防止连打(双击)