// start: XXX end: XXX consume: XXX
console.log(new Model1().getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
// start: XXX end: XXX consume: XXX
console.log(Model2.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
接下来,我们想控制其中一个Model的函数不可被其他人修改覆盖,所以要添加一些新的逻辑:
function wrap(Model, key) {
// 获取Class对应的原型
let target = Model.prototype
// 获取函数对应的描述符
let 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.constructor === A, target === A.prototype) // true, true
}
函数tag与method会在class A定义的时候执行。
@Decorator 在 Class 中的使用
该装饰器会在class定义前调用,如果函数有返回值,则会认为是一个新的构造函数来替代之前的构造函数。
函数接收一个参数:
1.constructor 之前的构造函数
我们可以针对原有的构造函数进行一些改造:
新增一些属性
如果想要新增一些属性之类的,有两种方案可以选择:
1.创建一个新的class继承自原有class,并添加属性
2.针对当前class进行修改
后者的适用范围更窄一些,更接近mixin的处理方式。
@name
class Person {
sayHi() {
console.log(`My name is: ${this.name}`)
}
}