ES6 已经来了,ES7 还会远么?
ES6 标准已于上个月(2015年6月17日)正式发布,众多 新特性 成为标准固然另人激动,然而更值得憧憬的还是未来。所以,让我们来看看 ES7(更正式的说法是 ES2016)有哪些激动人心的变化。最为人津津乐道的可能就是 async/await 了,不过我个人非常喜欢 Yehuda Katz 提出的 decorator 模式,本文尝试对此做一个介绍。
decorator 是什么
ES7 的 decorator 概念是从 Python 借来的,在 Python 里,decorator 实际上是一个 wrapper,它作用于一个目标函数,对这个目标函数做一些额外的操作,然后返回一个新的函数:
def my_decorator(fn):
def inner(name):
print 'Hello ' + fn(name)
return inner
@my_decorator
def greet(name):
return name
greet('Decorator!')
# Hello Decorator!
这种 @decorator
的写法其实是一种语法糖,从 my_decorator
的定义就可以看出,它接收一个函数( fn
)为参数,定义一个新的内部函数( innner
),这个内部函数会定义一些行为,最后 my_decorator
返回这个内部函数( inner
)。
上面的 @my_decorator
等于:
greet = my_decorator(greet)
greet('Decorator!')
# Hello 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
先来看一个简单的类:
class Dog {
bark () {
return 'wang!wang!'
}
}
如果我们想让 bark
这个方法成为一个只读的属性,那么可以定义一个 readonly
的 decorator:
// 注意这里的 `target` 是 `Dog.prototype`
function readonly(target, key, descriptor) {
descriptor.writable = false
return descriptor
}
可以看到,decorator 就是一个普通函数,只不过它接收 3 个参数,与 Object.defineProperty
一致。具体在这里,我们就是把 descriptor 的 writable
字段设为 false
。
然后把 readonly
作用到 bark
方法上:
class Dog {
@readonly
bark () {
return 'wang!wang!'
}
}
let dog = new Dog()
dog.bark = 'bark!bark!'
// Cannot assign to read only property 'bark' of [object Object]
@readonly
具体做了什么呢?我们先来看一下 ES6 的 class 在转换为 ES5 代码之后是什么样的,即 Dog
这个 class 等价于:
// 步骤 1
function Dog () {}
// 步骤 2
Object.defineProperty(Dog.prototype, 'bark', {
value: function () { return 'wang!wang!' },
enumerable: false,
configurable: true,
writable: true
})
对 bark
方法应用 @readonly
之后,JS 引擎就会在进行步骤二之前调用这个 decorator:
let descriptor = {
value: function () { return 'wang!wang!' },
enumerable: false,
configurable: true,
writable: true
}
// decorator 接收的参数与 Object.defineProperty 一致
descriptor = readonly(Dog.prototype, 'bark', descriptor) || descriptor
Object.defineProperty(Dog.prototype, 'bark', descriptor)
所以,ES7 的 decorator,作用就是返回一个新的 descriptor,并把这个新返回的 descriptor 应用到目标方法上。稍后我们将会看到,decorator 并非只能作用到类的方法/属性上,它还可以作用到类本身。
作用在类上的 decorator
作用在方法上的 decorator 接收的第一个参数( target
)是类的 prototype;如果把一个 decorator 作用到类上,则它的第一个参数 target
是类本身:
// 这里的 `target` 是类本身
function doge (target) {
target.isDoge = true
}
@doge
class Dog {}
console.log(Dog.isDoge)
// true
decorator 也可以是 factory function
如果我们想对不同的目标对象应用同一个 decorator,但同时又需要有一些差别,怎么办?很简单:
function doge (isDoge) {
return function(target) {
target.isDoge = isDoge
}
}
@doge(true)
class Dog {}
console.log(Dog.isDoge)
// true
@doge(false)
class Human {}
console.log(Human.isDoge)
// false
对方法来说也是类似的:
function enumerable (isEnumerable) {
return function(target, key, descriptor) {
descriptor.enumerable = isEnumerable
}
}
class Dog {
@enumerable(false)
eat () { }
}
decrator 能做什么
在 Python 里,decorator 的作用非常丰富,比如可以在使用 threading 时候简化分配锁和解锁的步骤,进行用户认证,定义一些快捷方式(如 @staticmethod
, @classmethod
之类),定义后端 api 的路由(如 Flask 框架)等等。
对 ES7 来说,现在已经有人写了一个 core-decorators ,提供了一些非常实用的 decorator。
比如 @deprecate
:
import { deprecate } from 'core-decorators'
class Dog {
@deprecate
peeInRoom () {}
@deprecate('I am a good dog.')
peeInBed () {}
}
let dog = new Dog()
dog.peeInRoom()
// DEPRECATION Dog#peeInRoom: This function will be removed in future versions.
dog.peeInBed()
// DEPRECATION Dog#peeInBed: I am a good dog.
除此之外, raganwald ( JavaScript Allongé, the “Six” Edition 的作者)写了 一篇文章 介绍如何使用 decorator 来实现 mixin。这对框架和类库作者来说是一个重磅好消息。
现在就想用?
decorator 目前还只是一个提议,但是,感谢 Babel ,我们现在就可以体验它了。首先,安装 babel:
npm install babel -g
然后,开启 decorator:
babel --optional es7.decorators foo.js > foo.es5.js
babel 也提供了一个在线的 REPL ,勾选 experimental 选项,就可以了。
总结
decorator 让我们有机会在代码的执行期间改变其行为,我相信它在 Python 中可以做到的事情,在 ES7 中也同样能够做到。
参考
- Exploring ES2016 Decorators
- https://github.com/wycats/javascript-decorators
- https://github.com/jayphelps/core-decorators.js
- http://raganwald.com/2015/06/26/decorators-in-es7.html