1、装饰器是什么?
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上,是一种在不改变原类和使用继承的情况下,动态地扩展对象功能。
装饰器本质不是什么高大上的结构,就是一个普通的函数,
@expression
的形式其实是
Object.defineProperty
的语法糖,
expression
求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
2、装饰器怎么用?
类的装饰器可以装饰:
- 类
- 方法/属性
- 参数
- 访问器
类装饰
如下,我们声明一个Person类
class Person{
constructor(name) {
this.name = name
}
}
接着,创建一个实例对象
let person = new Person("leo");
console.log(person.name); // leo
以上只是一个使用类的简单例子。真实场景下,类中可能会有许多属性与方法,其也是我们使用类的目的,可以通过它,构造一类拥有相同属性与方法的实例对象。
那么当我们声明了类之后,假如某个程序片段需要使用该类来构造实例对象,且除了该类中的属性与方法,我们需要额外新增属性或方法,这时就可以使用装饰器来扩展原有的功能。
实例
// @装饰的必须是函数,该处为addAge
function addAge(target) { // 在修饰类时,此处target为Person这个类
target.prototype.age = 18;
}
// 类修饰,扩展功能(添加属性age,值为18)
@addAge
class Person{
constructor(name) {
this.name = name;
}
}
let person = new Person('leo');
console.log(person.name); // leo
console.log(person.age); // 18
在以上实例中,Person类中并不存在age属性,当装饰器作为修饰类的时候,会把构造器传递进去,
target.prototype.age
就是在每一个实例化对象上面添加一个
age
属性。
由于装饰器是表达式,我们也可以在装饰器后面再添加个参数
function addAge(number) {
return function(target){
target.prototype.age = number;
}
}
@addAge(18)
class Person{
constructor(name) {
this.name = name;
}
}
let person = new Person('leo');
console.log(person.name); // leo
console.log(person.age); // 18
方法/属性装饰
同样,装饰器可以用于修饰类的方法,这时候装饰器函数接收的参数变成了:
- target:对象的原型
- propertyKey:方法的名称
- descriptor:方法的属性描述符
看到这,有没有一丝熟悉感?这三个属性实际就是
Object.defineProperty
的三个参数。如果是类的属性,则不传递第三个参数。
实例
// 声明装饰器修饰方法/属性
function method(target, propertyKey, descriptor) {
console.log(target);
console.log("propertyKey:" + propertyKey);
console.log("descriptor:" + JSON.stringify(descriptor) + "\n\n");
descriptor.writable = false; // 默认为true,设置为false,则修饰的方法不可修改
};
function property(target, propertyKey) {
console.log("target:", target)
console.log("propertyKey:", propertyKey)
}
class Person{
constructor() {
this.name = 'leo';
}
@method
sayHi(){
return 'Hi' + this.name;
}
@method
static run(){
return 'static method';
}
}
const obj = new Person();
控制台打印如下
Person类中并没有打印的逻辑,这些是装饰器带来的扩展功能。我们尝试修改sayHi方法,如下
obj.sayHi = function(){
return "是否允许修改?"
}
此时控制台报错
因为我们在装饰器中设置 descriptor.writable = false,修饰某方法时,该方法只读,不允许修改。
3、装饰器工厂
如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再返回一个函数即可,如上面例子
function addAge(number) {
return function(target){
target.prototype.age = number;
}
}
@addAge(18)
class Person{
constructor(name) {
this.name = name;
}
}
let person = new Person('leo');
console.log(person.name); // leo
console.log(person.age); // 18
4、执行顺序
当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,如下
function f() {
console.log("f(): 我是f()");
return function (target, propertyKey, descriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): 我是g()");
return function (target, propertyKey, descriptor) {
console.log("g(): called");
}
}
class Person {
@f()
@g()
method() {}
}
控制台打印如下
表达式由上往下执行,而求值的结果函数由下往上调用。
5、应用场景
使用装饰器存在两个显著的优点:
- 代码可读性变强,装饰器命名相当于一个注释
- 不改变原有代码情况下,对原有功能进行扩展
借助装饰器的特性,除了提高可读性之外,对已经存在的类,可以通过装饰器在不改变原本代码的情况下,对原来功能进行扩展。
版权归原作者 前端不释卷leo 所有, 如有侵权,请联系我们删除。