ES6 已经来了,ES7 还会远么?
ES6 标准已于上个月(2015年6月17日)正式发布,众多新特性成为标准固然另人激动,然而更值得憧憬的还是未来。所以,让我们来看看 ES7(更正式的说法是 ES2016)有哪些激动人心的变化。最为人津津乐道的可能就是 async/await 了,不过我个人非常喜欢 Yehuda Katz 提出的 decorator 模式,本文尝试对此做一个介绍。
decorator 是什么
ES7 的 decorator 概念是从 Python 借来的,在 Python 里,decorator 实际上是一个 wrapper,它作用于一个目标函数,对这个目标函数做一些额外的操作,然后返回一个新的函数:
|
|
这种 @decorator
的写法其实是一种语法糖,从 my_decorator
的定义就可以看出,它接收一个函数(fn
)为参数,定义一个新的内部函数(innner
),这个内部函数会定义一些行为,最后 my_decorator
返回这个内部函数(inner
)。
上面的 @my_decorator
等于:
|
|
ES7 中的 decorator 同样借鉴了这个语法糖,不过依赖于 ES5 的 Object.defineProperty
方法 。
关于 Object.defineProperty 的一切(些)
defineProperty
所做的事情就是,为一个对象增加新的属性,或者更改对象某个已存在的属性。调用方式是 Object.defineProperty(obj, prop, descriptor)
,这 3 个参数分别代表:
- obj: 目标对象
- prop: 属性名
- descriptor: 针对该属性的描述符
有意思的是 descriptor
参数,它其实也是一个对象,其字段决定了 obj
的 prop
属性的一些特性。比如 enumerable
的真假就能决定目标对象是否可枚举(能够在 for…in 循环中遍历到,或者出现在 Object.keys
方法的返回值中),writable
决定目标对象的属性是否可以更改,等等。完整的描述符可选字段可以参看这里。
作用在方法上的 decorator
先来看一个简单的类:
|
|
如果我们想让 bark
这个方法成为一个只读的属性,那么可以定义一个 readonly
的 decorator:
|
|
可以看到,decorator 就是一个普通函数,只不过它接收 3 个参数,与 Object.defineProperty
一致。具体在这里,我们就是把 descriptor 的 writable
字段设为 false
。
然后把 readonly
作用到 bark
方法上:
|
|
@readonly
具体做了什么呢?我们先来看一下 ES6 的 class 在转换为 ES5 代码之后是什么样的,即 Dog
这个 class 等价于:
|
|
对 bark
方法应用 @readonly
之后,JS 引擎就会在进行步骤二之前调用这个 decorator:
|
|
所以,ES7 的 decorator,作用就是返回一个新的 descriptor,并把这个新返回的 descriptor 应用到目标方法上。稍后我们将会看到,decorator 并非只能作用到类的方法/属性上,它还可以作用到类本身。
作用在类上的 decorator
作用在方法上的 decorator 接收的第一个参数(target
)是类的 prototype;如果把一个 decorator 作用到类上,则它的第一个参数 target
是类本身:
|
|
decorator 也可以是 factory function
如果我们想对不同的目标对象应用同一个 decorator,但同时又需要有一些差别,怎么办?很简单:
|
|
对方法来说也是类似的:
|
|
decrator 能做什么
在 Python 里,decorator 的作用非常丰富,比如可以在使用 threading 时候简化分配锁和解锁的步骤,进行用户认证,定义一些快捷方式(如 @staticmethod
,@classmethod
之类),定义后端 api 的路由(如 Flask 框架)等等。
对 ES7 来说,现在已经有人写了一个 core-decorators,提供了一些非常实用的 decorator。
比如 @deprecate
:
|
|
除此之外,raganwald(JavaScript Allongé, the “Six” Edition 的作者)写了一篇文章介绍如何使用 decorator 来实现 mixin。这对框架和类库作者来说是一个重磅好消息。
现在就想用?
decorator 目前还只是一个提议,但是,感谢 Babel,我们现在就可以体验它了。首先,安装 babel:
|
|
然后,开启 decorator:
|
|
babel 也提供了一个在线的 REPL,勾选 experimental 选项,就可以了。
总结
decorator 让我们有机会在代码的执行期间改变其行为,我相信它在 Python 中可以做到的事情,在 ES7 中也同样能够做到。