迭代器、生成器、数组操作,JavaScript和Python变得越来越像。今天我们来探讨ECMAScript的装饰器 —— 又一个Python化的JavaScript特性。
装饰器模式
装饰器到底是什么东西?在Python中,装饰器是调用高阶函数的一种非常简单的语法。 一个Python 的装饰器,实际上就是以另一个函数为参数的函数, 它以非常简洁的语法来增强参数函数的行为。 最简单的Python装饰器看起来像这样:
|
|
第一行的@mydecorator
就是一个装饰器。@
用来告诉Python解析器,我们将调用一个名为mydecorator
的函数来增强函数myfunc
,这将得到一个新的函数 —— 它可以增强myfunc
的功能。
ES5和ES2015中的装饰器
在ES5中,实现指令式的装饰器(作为纯函数)非常琐碎。 在ES2015中,由于类支持继承, 我们需要更好的 方法来实现在多个类之间共用同一功能。
Yehuda的装饰器实现是通过使用注解并在设计时修改JavaScript类、属性和对象来实现,他保持了装饰器的 声明式风格。
现在,让我们看一下ES2016中的装饰器的实战!
ES2016装饰器实战
请记住我们从Python中学到的东西。一个ES2016装饰器就是一个返回函数的表达式,它的参数为target、name
和descriptor。 使用@
符号来为类或属性应用一个装饰器。
装饰一个属性
让我们看一个非常简单的类,Cat:
|
|
执行这段语句,就会在原型对象Cat.prototype
上添加一个meow
方法,类似于:
|
|
假设我们希望将一个属性或方法标记为只读
的。我们可以定义一个@readonly
装饰器:
|
|
然后,我们可以在meow
方法上应用这个装饰器:
|
|
一个装饰器不过就是一个返回函数的表达式。因此,装饰器可以带参数,比如@readonly
和
@something(parameter)
都是合法的。
现在,在把描述子安装到Cat.prototype
上面之前,引擎将首先调用装饰器:
|
|
上面的代码执行后,meow
方法就变成只读了。我们可以验证一下:
|
|
执行到第2行时将抛出异常:Attempted to assign to readonly property
太棒了,对吗?
我们接下来将对类进行装饰。不过插一句,尽管JavaScript的装饰器概念还很新, ES2016的装饰器库已经开始出现了,比如Jay Phelps开发的core-decorators。
类似于我们上面的只读属性的尝试, Jay Phelps的库中已经包含了@readonly
的实现,只需要引入就可以直接使用:
|
|
类似的,运行上面的代码,将在试图对dinner.entree
进行改写时抛出异常:Cannot assign to read only property 'entree' of [object Object]。
Jay Phelps的库中还包含了一些其他常用的装饰器,例如@deprecate
,用来对可能变化的API提供警示信息:
|
|
装饰一个类
接下来让我们看一下如何对类进行装饰。在这个案例中,一个装饰器将使用被装饰类的构造
函数作为参数。 对于一个假想的MySuperHero
类,我们可以定义一个简单的装饰器@superhero
:
|
|
我们可以更进一步, 把装饰器函数定义为一个可接受参数的工厂函数:
|
|
ES2016装饰器可以作用于属性描述子和类。它们可以自动地获得传入的属性名和目标对象,
我们很快介绍这一点。 由于可以获取到属性描述子,这使得一个装饰器可以做很多事情,例如
可以将一个属性改变为使用getter
,这对于实现自动的数据绑定相当有用(mobx就是这么干的)。
ES2016装饰器和Mixins
我认真阅读了Reg Braithwaite最近发表的关于ES2016装饰器用作mixin的文章。Reg提出了一种方法
可以将功能混入
任何目标(类原型或独立对象),并且描述了一个类特定的版本。函数式mixin可以
将实例行为混入一个类的原型,看起来大致如下:
|
|
特别好。我们现在可以定义一些mixin,然后使用它们来装饰类。现在假设有一个类
ComicBookCharacter
:
|
|
也许这时世界上最乏味的角色, 但是我们可以定义一些mixins来给这个角色
赋予超能SuperPowers
和工具带UtilityBelt
。让我们使用Reg的mixin辅助函数来实现:
|
|
有了这些做基础,我们可以使用@
语法来赋予 ComicBookCharacter
能力。注意
我们是如何为类应用多个装饰器的:
|
|
现在,让我们使用这个类来创建一个蝙蝠侠角色:
|
|
这些用于类的装饰器相对紧凑,可以用它们来代替高阶函数调用。
开启Babel的装饰器功能
装饰器目前还停留在建议阶段,并未被最终批准。但是多种编译器都已经 支持这一特性,例如Babel、traceur和tsc。
如果在命令行中使用Babel,可以如下方式打开装饰器功能:
|
|
或者,也使用转换器打开这个功能:
|
|
有趣的实验
Paul Lewis, who luckily enough I sit next to, has been experimenting with decorators as a means for rescheduling code that reads and writes to the DOM. It borrows ideas from Wilson Page’s FastDOM, but provides an otherwise small API surface. Paul’s read/write decorators can also warn you via the console if you’re calling methods or properties during a @write (or change the DOM during @read) that trigger layout.
坐在我旁边的Paul Lewis尝试使用装饰器来重新调度读写DOM的代码。他借鉴了Wilson Page的FastDOM实现思路,但是提供了非常简单的API。
下面是Paul的一个实验示例,试图在@read
中改变DOM将在控制台输出警告:
|
|
开始尝试装饰器!
短期来看,ES2016装饰器对于声明式装饰、注解、类型检查等都很有用。从 长远的角度,它们将对静态语言分析(编译时类型检查、自动完成等)非常有用。
They aren’t that different from decorators in classic OOP, where the pattern allows an object to be decorated with behaviour, either statically or dynamically without impacting objects from the same class. I think they’re a neat addition. The semantics for decorators on class properties are still in flux, however keep an eye on Yehuda’s repo for updates.
ES2016的装饰器和经典OOP中的装饰器没有特别的差异。在传统OOP中,装饰器模式 允许为一个对象赋以额外的行为能力,这既可以是静态的装饰,也可以是动态的装饰—— 在这种情况下该类的其他实例不受影响。ES2016的装饰器语法还在变迁当中,可以留意 Yehuda的repo更新。
库作者们现在在讨论在什么地方可以使用装饰器代替mixins,显然,这些装饰器也可以用于 React中的高阶组件。
我个人对于围绕装饰器的实验和应用相当兴奋,也希望你们使用Babel来试一下,找到 并实现可以复用的装饰器,没准你也可以和Paul一样,分享你的成果!
进一步的阅读和参考
- https://github.com/wycats/javascript-decorators
- https://github.com/jayphelps/core-decorators.js
- http://blog.developsuperpowers.com/eli5-ecmascript-7-decorators/
- http://elmasse.github.io/js/decorators-bindings-es7.html
- http://raganwald.com/2015/06/26/decorators-in-es7.html
- Jay’s function expression ES2016 Decorators example
感谢Jay Phelps, Sebastian McKenzie, Paul Lewis 和 Surma 帮助审核这篇文章并提供了详细的反馈意见。
原文:Exploring EcmaScript Decorators by Addy Osmani