什么是装饰器
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。我们在程序开发中,许多时候并不希望某个类天生就非常庞大,一次性包含许多职责。装饰者模式可以动态地给某个对象添加一些额外的功能,而不会影响从这个类中派生的其他对象。
JS的装饰器
为了更简便的使用装饰器模式,ES7引入了一个新的语法,依赖于 ES5 的Object.defineProperty方法。
修饰器的用法
类的装饰
class A {}
// 等同于
class A {}
A = decorator(A) || A;
也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要修饰的目标类。
如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
简单来说JS 的装饰器可以用来“装饰”三种类型的对象:类的属性/方法、访问器、类本身
针对属性/方法的装饰器
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
function readonly(target, name, descriptor){
// descriptor对象原来的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);
- 装饰器第一个参数是类的原型对象,上例是 Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);
- 第二个参数是 所要装饰的属性名
- 第三个参数是 该属性的描述对象
函数方法的装饰
由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。
可以采用高阶函数的形式直接执行。
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
// decorator 外部可以包装一个函数,函数可以带参数
function Decorator(type){
/**
* 这里是真正的 decorator
* @target 装饰的属性所述的类的原型,注意,不是实例后的类。如果装饰的是 Car 的某个属性,这个 target 的值就是 Car.prototype
* @name 装饰的属性的 key
* @descriptor 装饰的对象的描述对象
*/
return function (target, name, descriptor){
// 以此可以获取实例化的时候此属性的默认值
let v = descriptor.initializer && descriptor.initializer.call(this);
// 返回一个新的描述对象,或者直接修改 descriptor 也可以
return {
enumerable: true,
configurable: true,
get: function() {
return v;
},
set: function(c) {
v = c;
}
}
}
}