装饰器语法糖背后的故事
所谓语法糖,往往意味着“美好的表象”。正如 class 语法糖背后是大家早已十分熟悉的 ES5 构造函数一样,装饰器语法糖背后也是我们的老朋友,不信我们一起来看看@decorator
都帮我们做了些什么:
Part1:函数传参&调用
上一节我们使用 ES6 实现装饰器模式时曾经将按钮实例传给了 Decorator,以便于后续 Decorator 可以对它进行逻辑的拓展。这也正是装饰器的最最基本操作——定义装饰器函数,将被装饰者“交给”装饰器。这也正是装饰器语法糖首先帮我们做掉的工作 —— 函数传参&调用。
类装饰器的参数
当我们给一个类添加装饰器时:
function classDecorator(target) {
target.hasDecorator = true
return target
}
// 将装饰器“安装”到Button类上
@classDecorator
class Button {
// Button类的相关逻辑
}
方法装饰器的参数
而当我们给一个方法添加装饰器时:
function funcDecorator(target, name, descriptor) {
let originalMethod = descriptor.value
descriptor.value = function() {
console.log('我是Func的装饰器逻辑')
return originalMethod.apply(this, arguments)
}
return descriptor
}
class Button {
@funcDecorator
onClick() {
console.log('我是Func的原有逻辑')
}
}
此处的 target 变成了Button.prototype
,即类的原型对象。这是因为 onClick 方法总是要依附其实例存在的,修饰 onClik 其实是修饰它的实例。但我们的装饰器函数执行的时候,Button 实例还并不存在。为了确保实例生成后可以顺利调用被装饰好的方法,装饰器只能去修饰 Button 类的原型对象。
装饰器函数调用的时机
装饰器函数执行的时候,Button 实例还并不存在。这是因为实例是在我们的代码运行时动态生成的,而装饰器函数则是在编译阶段就执行了。所以说装饰器函数真正能触及到的,就只有类这个层面上的对象。
Part2:将“属性描述对象”交到你手里
在编写类装饰器时,我们一般获取一个target参数就足够了。但在编写方法装饰器时,我们往往需要至少三个参数:
function funcDecorator(target, name, descriptor) {
let originalMethod = descriptor.value
descriptor.value = function() {
console.log('我是Func的装饰器逻辑')
return originalMethod.apply(this, arguments)
}
return descriptor
}
第一个参数的意义,前文已经解释过。第二个参 数name,是我们修饰的目标属性属性名,也没啥好讲的。关键就在这个 descriptor 身上,它也是我们使用频率最高的一个参数,它的真面目就是“属性描述对象”(attributes object)。这个名字大家可能不熟悉,但Object.defineProperty
方法我想大家多少都用过,它的调用方式是这样的:
Object.defineProperty(obj, prop, descriptor)
此处的descriptor和装饰器函数里的 descriptor 是一个东西,它是 JavaScript 提供的一个内部数据结构、一个对象,专门用来描述对象的属性。它由各种各样的属性描述符组成,这些描述符又分为数据描述符和存取描述符:
- 数据描述符:包括 value(存放属性值,默认为默认为 undefined)、writable(表示属性值是否可改变,默认为true)、enumerable(表示属性是否可枚举,默认为 true)、configurable(属性是否可配置,默认为true)。
- 存取描述符:包括
get
方法(访问属性时调用的方法,默认为 undefined),set
(设置属性时调用的方法,默认为 undefined )
很明显,拿到了 descriptor,就相当于拿到了目标方法的控制权。通过修改 descriptor,我们就可以对目标方法为所欲为的逻辑进行拓展了~
在上文的示例中,我们通过 descriptor 获取到了原函数的函数体(originalMethod),把原函数推迟到了新逻辑(console)的后面去执行。这种做法和我们上一节在ES5中实现装饰器模式时做的事情一模一样,所以说装饰器就是这么回事儿,换汤不换药~