0


我与 JavaScript 装饰器(Decorator)的一次碰面

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、应用场景

使用装饰器存在两个显著的优点:

  • 代码可读性变强,装饰器命名相当于一个注释
  • 不改变原有代码情况下,对原有功能进行扩展

借助装饰器的特性,除了提高可读性之外,对已经存在的类,可以通过装饰器在不改变原本代码的情况下,对原来功能进行扩展。


本文转载自: https://blog.csdn.net/qq_41809113/article/details/122350660
版权归原作者 前端不释卷leo 所有, 如有侵权,请联系我们删除。

“我与 JavaScript 装饰器(Decorator)的一次碰面”的评论:

还没有评论