# js装饰器

# 方法上的decorator

先来看一个简单的类:

class Dog {
  bark () {
    return 'wang!wang!'
  }
}

如果想让 bark 这个方法成为一个只读的属性,那么可以定义一个 readonly 的 decorator:

// 注意这里的target是Dog.prototype
function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor
}

可以看到,decorator 就是一个普通函数,只不过它接收 3 个参数,与 Object.defineProperty 一致。就是把 descriptor 的 writable 字段设为 false 。

然后把 readonly 作用到 bark 方法上:

class Dog {
  @readonly
  bark () {
    return 'wang!wang!'
  }
}

let dog = new Dog()
dog.bark = 'bark!bark!'
// Cannot assign to read only property 'bark' of [object Object]

@readonly 具体做了什么呢?即 Dog 这个 class 等价于:

// 步骤 1
function Dog () {}

// 步骤 2
Object.defineProperty(Dog.prototype, 'bark', {
  value: function () { return 'wang!wang!' },
  enumerable: false,
  configurable: true,
  writable: true
})

对 bark 方法应用 @readonly 之后,JS 引擎就会在进行步骤二之前调用这个 decorator:

let descriptor = {
  value: function () { return 'wang!wang!' },
  enumerable: false,
  configurable: true,
  writable: true
}

// decorator 接收的参数与 Object.defineProperty 一致
descriptor = readonly(Dog.prototype, 'bark', descriptor) || descriptor
Object.defineProperty(Dog.prototype, 'bark', descriptor)

所以,ES7 的 decorator,作用就是 返回一个新的 descriptor,并把这个新返回的 descriptor 应用到目标方法上。decorator 并非只能作用到类的方法/属性上,它还可以作用到类本身

# 类上的 decorator

作用在方法上的 decorator 接收的第一个参数( target )是类的 prototype;如果把一个 decorator 作用到类上,则它的第一个参数 target 是类本身:

// 这里的 `target` 是类本身
function doge (target) {
  target.isDoge = true
}

@doge
class Dog {}

console.log(Dog.isDoge)
// true

# decorator 也可以是 factory function

如果想对不同的目标对象应用同一个 decorator,但同时又需要有一些差别,怎么办?很简单:

function doge (isDoge) {
  return function(target) {
    target.isDoge = isDoge
  }
}

@doge(true)
class Dog {}

console.log(Dog.isDoge)
// true

@doge(false)
class Human {}
console.log(Human.isDoge)
// false

对方法来说也是类似的:

function enumerable (isEnumerable) {
  return function(target, key, descriptor) {
    descriptor.enumerable = isEnumerable
  }
}

class Dog {
  @enumerable(false)
  eat () { }
}

多个装饰器时,类似洋葱圈模式,先从外到内,再有内到外

function dec(id){
 console.log('evaluated', id);
 return (target, property, descriptor) =>console.log('executed', id);
}
class Example {
 @dec(1)
 @dec(2)
 method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

应用场景:react-redux的connect,类的混入某些新属性或者修改属性

目前如果想要使用,要在babel中配置。

最后更新: 4/21/2024, 8:03:54 AM