Javascript装饰器的妙用(3)

// 创建一个继承自Person的匿名类
// 直接返回并替换原有的构造函数
function name(constructor) {
  return class extends constructor {
    name = 'Niko'
  }
}

new Person().sayHi()

修改原有属性的描述符

@seal
class Person {
  sayHi() {}
}

function seal(constructor) {
  let descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, 'sayHi')
  Object.defineProperty(constructor.prototype, 'sayHi', {
    ...descriptor,
    writable: false
  })
}

Person.prototype.sayHi = 1 // 无效

使用闭包来增强装饰器的功能


在TS文档中被称为装饰器工厂

因为@符号后边跟的是一个函数的引用,所以对于mixin的实现,我们可以很轻易的使用闭包来实现:

class A { say() { return 1 } }
class B { hi() { return 2 } }

@mixin(A, B)
class C { }

function mixin(...args) {
  // 调用函数返回装饰器实际应用的函数
  return function(constructor) {
    for (let arg of args) {
      for (let key of Object.getOwnPropertyNames(arg.prototype)) {
        if (key === 'constructor') continue // 跳过构造函数
        Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key))
      }
    }
  }
}

let c = new C()
console.log(c.say(), c.hi()) // 1, 2

多个装饰器的应用

装饰器是可以同时应用多个的(不然也就失去了最初的意义)。
用法如下:

@decorator1
@decorator2
class { }

执行的顺序为decorator2 -> decorator1,离class定义最近的先执行。
可以想像成函数嵌套的形式:

decorator1(decorator2(class {}))

@Decorator 在 Class 成员中的使用

类成员上的 @Decorator 应该是应用最为广泛的一处了,函数,属性,get、set访问器,这几处都可以认为是类成员。
在TS文档中被分为了Method Decorator、Accessor Decorator和Property Decorator,实际上如出一辙。

关于这类装饰器,会接收如下三个参数:
1.如果装饰器挂载于静态成员上,则会返回构造函数,如果挂载于实例成员上则会返回类的原型
2.装饰器挂载的成员名称
3.成员的描述符,也就是Object.getOwnPropertyDescriptor的返回值


Property Decorator不会返回第三个参数,但是可以自己手动获取
前提是静态成员,而非实例成员,因为装饰器都是运行在类创建时,而实例成员是在实例化一个类的时候才会执行的,所以没有办法获取对应的descriptor

静态成员与实例成员在返回值上的区别

可以稍微明确一下,静态成员与实例成员的区别:

class Model {
  // 实例成员
  method1 () {}
  method2 = () => {}

// 静态成员
  static method3 () {}
  static method4 = () => {}
}

method1和method2是实例成员,method1存在于prototype之上,而method2只在实例化对象以后才有。
作为静态成员的method3和method4,两者的区别在于是否可枚举描述符的设置,所以可以简单地认为,上述代码转换为ES5版本后是这样子的:

function Model () {
  // 成员仅在实例化时赋值
  this.method2 = function () {}
}

// 成员被定义在原型链上
Object.defineProperty(Model.prototype, 'method1', {
  value: function () {},
  writable: true,
  enumerable: false,  // 设置不可被枚举
  configurable: true
})

// 成员被定义在构造函数上,且是默认的可被枚举
Model.method4 = function () {}

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/0ec0211cd10497b667f1909d61f79821.html