0


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

1、装饰器是什么?

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上,是一种在不改变原类和使用继承的情况下,动态地扩展对象功能。

装饰器本质不是什么高大上的结构,就是一个普通的函数,

  1. @expression

的形式其实是

  1. Object.defineProperty

的语法糖,

  1. expression

求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

2、装饰器怎么用?

类的装饰器可以装饰:

  • 方法/属性
  • 参数
  • 访问器

类装饰

如下,我们声明一个Person类

  1. class Person{
  2. constructor(name) {
  3. this.name = name
  4. }
  5. }

接着,创建一个实例对象

  1. let person = new Person("leo");
  2. console.log(person.name); // leo

以上只是一个使用类的简单例子。真实场景下,类中可能会有许多属性与方法,其也是我们使用类的目的,可以通过它,构造一类拥有相同属性与方法的实例对象。

那么当我们声明了类之后,假如某个程序片段需要使用该类来构造实例对象,且除了该类中的属性与方法,我们需要额外新增属性或方法,这时就可以使用装饰器来扩展原有的功能。

实例

  1. // @装饰的必须是函数,该处为addAge
  2. function addAge(target) { // 在修饰类时,此处target为Person这个类
  3. target.prototype.age = 18;
  4. }
  5. // 类修饰,扩展功能(添加属性age,值为18)
  6. @addAge
  7. class Person{
  8. constructor(name) {
  9. this.name = name;
  10. }
  11. }
  12. let person = new Person('leo');
  13. console.log(person.name); // leo
  14. console.log(person.age); // 18

在以上实例中,Person类中并不存在age属性,当装饰器作为修饰类的时候,会把构造器传递进去,

  1. target.prototype.age

就是在每一个实例化对象上面添加一个

  1. age

属性。

由于装饰器是表达式,我们也可以在装饰器后面再添加个参数

  1. function addAge(number) {
  2. return function(target){
  3. target.prototype.age = number;
  4. }
  5. }
  6. @addAge(18)
  7. class Person{
  8. constructor(name) {
  9. this.name = name;
  10. }
  11. }
  12. let person = new Person('leo');
  13. console.log(person.name); // leo
  14. console.log(person.age); // 18

方法/属性装饰

同样,装饰器可以用于修饰类的方法,这时候装饰器函数接收的参数变成了:

  • target:对象的原型
  • propertyKey:方法的名称
  • descriptor:方法的属性描述符

看到这,有没有一丝熟悉感?这三个属性实际就是

  1. Object.defineProperty

的三个参数。如果是类的属性,则不传递第三个参数。

实例

  1. // 声明装饰器修饰方法/属性
  2. function method(target, propertyKey, descriptor) {
  3. console.log(target);
  4. console.log("propertyKey:" + propertyKey);
  5. console.log("descriptor:" + JSON.stringify(descriptor) + "\n\n");
  6. descriptor.writable = false; // 默认为true,设置为false,则修饰的方法不可修改
  7. };
  8. function property(target, propertyKey) {
  9. console.log("target:", target)
  10. console.log("propertyKey:", propertyKey)
  11. }
  12. class Person{
  13. constructor() {
  14. this.name = 'leo';
  15. }
  16. @method
  17. sayHi(){
  18. return 'Hi' + this.name;
  19. }
  20. @method
  21. static run(){
  22. return 'static method';
  23. }
  24. }
  25. const obj = new Person();

控制台打印如下

Person类中并没有打印的逻辑,这些是装饰器带来的扩展功能。我们尝试修改sayHi方法,如下

  1. obj.sayHi = function(){
  2. return "是否允许修改?"
  3. }

此时控制台报错

因为我们在装饰器中设置 descriptor.writable = false,修饰某方法时,该方法只读,不允许修改。

3、装饰器工厂

如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再返回一个函数即可,如上面例子

  1. function addAge(number) {
  2. return function(target){
  3. target.prototype.age = number;
  4. }
  5. }
  6. @addAge(18)
  7. class Person{
  8. constructor(name) {
  9. this.name = name;
  10. }
  11. }
  12. let person = new Person('leo');
  13. console.log(person.name); // leo
  14. console.log(person.age); // 18

4、执行顺序

当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,如下

  1. function f() {
  2. console.log("f(): 我是f()");
  3. return function (target, propertyKey, descriptor) {
  4. console.log("f(): called");
  5. }
  6. }
  7. function g() {
  8. console.log("g(): 我是g()");
  9. return function (target, propertyKey, descriptor) {
  10. console.log("g(): called");
  11. }
  12. }
  13. class Person {
  14. @f()
  15. @g()
  16. method() {}
  17. }

控制台打印如下

表达式由上往下执行,而求值的结果函数由下往上调用。

5、应用场景

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

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

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


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

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

还没有评论