Decorators
装饰器(Decorator)用来增强 JavaScript 类(class)的功能,许多面向对象的语言都有这种语法,目前有一个提案将其引入了 ECMAScript。
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。 装饰器使用@expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
A First Class Decorator
类装饰器是我们最常使用到的,它的通常作用是,为该类扩展功能。
function Logger(constructor: Function) {
console.log("Logging...");
console.log(constructor);
}
@Logger
class Person {
name = "Jack";
constructor() {
console.log("Creating person object....");
}
}
const person = new Person();
console.log(person);
在这里,Logger
就是一个装饰器。使用的时候,在类声明前一行使用 @
后跟装饰器名字使用。这里用作类装饰器。
类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。
类装饰器表达式会在运行时当做函数被调用,类的构造函数函数作为其唯一的参数。
现在运行这段代码,看看会有什么。
Logging...
class Person {
construcotr() {
this.name = "Jack";
console.log("Creating person object....")
}
}
Creating person object....
Person { name: 'Jack' }
首先可以看的到,装饰器内的输出先于我们实例化 Person
的输出。装饰器在类被定义的时候执行,而不是实例化的时候,事实上不需要做实例化也会执行。
Why Decorator?
设想有这样一个场景。
目前有一个 Tank 类,有一个 Plane 类,有一个 Animal 类。这三个类都需要一个公共的方法来获取他们所在的位置。我们第一可能想到使用继承来实现。
class BaseClass {
getPosition() {
return {
x: 100,
y: 200,
z: 300,
};
}
}
class Tank extends BaseClass {
}
class Plane extends BaseClass {
}
class Animal extends BaseClass {
}
这样三个类都可以调用 getPosition
方法来获取各自的位置了。到目前为止看起来没什么问题。
现在又有了一个新的诉求,Tank
类和Plane
类需要一个新的方法addPetrol
来给坦克和飞机加油。而动物不需要加油。此时这种写法好像不能继续进行下去了。而 js 目前没有直接语法提供多继承的功能,我们的继承方式好像行不通了。这时候装饰器可以很完美的实现这样的功能。此时就可以请我们的装饰器闪亮登场了
装饰器功能之——能力扩展
我们把getPosition
和addPertrol
都抽象成一个单独的功能,它们得作用是给宿主扩展对应的功能。
const getPositionDecorator: ClassDecorator = (constructor: Function) => {
constructor.prototype.getPosition = () => {
return [100, 200];
};
};
const addPetrolDecorator: ClassDecorator = (constructor: Function) => {
constructor.prototype.addPetrol = () => {
// do something
console.log(`${
constructor.name}进行加油`);
};
};
@addPetrolDecorator
@getPositionDecorator
class Tank {
}
@addPetrolDecorator
@getPositionDecorator
class Plane {
}
@getPositionDecorator
class Animal {
}
这样的话,加入日后我们有其他的猫猫狗狗,都可以对他进行能力扩展,让其具有加油的能力。
多个装饰器叠加的时候,执行顺序为离被装饰对象越近的装饰器越先执行。下面有更详细的章节。
Working with a Decorator Factories
可以通过装饰器工厂创建装饰器
function Logger(logString: string) {
return function (constructor: Function) {
console.log(logString);
console.log(constructor);
};
}
@Logger("LOGGING - PERSON")
class Person2 {
name = "Jack";
constructor() {
console.log("Creating person object....");
}
}
在这里面我们返回一个函数,同时,我们有能力接受参数了,这让我们可以对装饰器有更高的灵活性,更多的可能性。实用性会更强。
Building More Useful Decorator
来看看装饰器还能干嘛
function WithTemplate(template: string, hookId: string) {
return function (constructor: any) {
const hookEl = document.getElementById(hookId);
const p = new constructor();
if (hookEl) {
hookEl.innerHTML = template;
hookEl.querySelector("h1")!.textContent = p.name;
}
};
}
@WithTemplate("<h1></h1>", "app")
class P {
namme = "Jack";