descriptor = Object.getOwnPropertyDescriptor(target, key)
Object.defineProperty(target, key, {
...descriptor,
writable: false // 设置属性不可被修改
})
}
wrap(Model1, 'getData')
Model1.prototype.getData = 1 // 无效
可以看出,两个wrap函数中有不少重复的地方,而修改程序行为的逻辑,实际上依赖的是Object.defineProperty中传递的三个参数。
所以,我们针对wrap在进行一次修改,将其变为一个通用类的转换:
function wrap(decorator) {
return function (Model, key) {
let target = Model.prototype
let dscriptor = Object.getOwnPropertyDescriptor(target, key)
decorator(target, key, descriptor)
}
}
let log = function (target, key, descriptor) {
// 将修改后的函数重新定义到原型链上
Object.defineProperty(target, key, {
...descriptor,
value: function (...arg) {
let start = new Date().valueOf()
try {
return descriptor.value.apply(this, arg) // 调用之前的函数
} finally {
let end = new Date().valueOf()
console.log(`start: ${start} end: ${end} consume: ${end - start}`)
}
}
})
}
let seal = function (target, key, descriptor) {
Object.defineProperty(target, key, {
...descriptor,
writable: false
})
}
// 参数的转换处理
log = wrap(log)
seal = warp(seal)
// 添加耗时统计
log(Model1, 'getData')
log(Model2, 'getData')
// 设置属性不可被修改
seal(Model1, 'getData')
到了这一步以后,我们就可以称log和seal为装饰器了,可以很方便的让我们对一些函数添加行为。
而拆分出来的这些功能可以用于未来可能会有需要的地方,而不用重新开发一遍相同的逻辑。
Class 中的作用
就像上边提到了,现阶段在JS中继承多个Class是一件头疼的事情,没有直接的语法能够继承多个 Class。
class A { say () { return 1 } }
class B { hi () { return 2 } }
class C extends A, B {} // Error
class C extends A extends B {} // Error
// 这样才是可以的
class C {}
for (let key of Object.getOwnPropertyNames(A.prototype)) {
if (key === 'constructor') continue
Object.defineProperty(C.prototype, key, Object.getOwnPropertyDescriptor(A.prototype, key))
}
for (let key of Object.getOwnPropertyNames(B.prototype)) {
if (key === 'constructor') continue
Object.defineProperty(C.prototype, key, Object.getOwnPropertyDescriptor(B.prototype, key))
}
let c = new C()
console.log(c.say(), c.hi()) // 1, 2
所以,在React中就有了一个mixin的概念,用来将多个Class的功能复制到一个新的Class上。
大致思路就是上边列出来的,但是这个mixin是React中内置的一个操作,我们可以将其转换为更接近装饰器的实现。
在不修改原Class的情况下,将其他Class的属性复制过来:
function mixin(constructor) {
return function (...args) {
for (let arg of args) {
for (let key of Object.getOwnPropertyNames(arg.prototype)) {
if (key === 'constructor') continue // 跳过构造函数
Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key))
}
}
}
}
mixin(C)(A, B)
let c = new C()
console.log(c.say(), c.hi()) // 1, 2
以上,就是装饰器在函数、Class上的实现方法(至少目前是的),但是草案中还有一颗特别甜的语法糖,也就是@Decorator了。
能够帮你省去很多繁琐的步骤来用上装饰器。
@Decorator的使用方法
草案中的装饰器、或者可以说是TS实现的装饰器,将上边的两种进一步地封装,将其拆分成为更细的装饰器应用,目前支持以下几处使用:
1.Class
2.函数
3.get set访问器
4.实例属性、静态函数及属性
5.函数参数
@Decorator的语法规定比较简单,就是通过@符号后边跟一个装饰器函数的引用:
@tag
class A {
@method
hi () {}
}
function tag(constructor) {
console.log(constructor === A) // true
}
function method(target) {
console.log(target.construc