Skip to content

ES6中的 Decorator  #96

Open
Open
@TieMuZhen

Description

@TieMuZhen

一、介绍

Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式

简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。

ES6Decorator功能亦如此,用于扩展类属性和类方法

这里定义一个学生,这时候他什么学号都没有

class student{ 
}

定义一个得到 学号的函数,即装饰器

function info(target){
    target.num= 123
}

使用该装饰器对学生进行增强

@info
class student{
}

这时候学生就有学号了

student.num // 123

上述代码虽然简单,但也能够清晰看到了使用Decorator两大优点:

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

二、用法

Docorator修饰对象为下面两种:

  • 类的装饰
  • 类属性的装饰

类的装饰

当对本身进行装饰的时候,能够接受一个参数,即类本身

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性

@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  // 添加属性
  target.isTestable = true;
 
  // 原型链上添加方法
  const prototype = target.prototype;
  prototype.getInfo = function(){
    console.log(this.isTestable);
  }
}

MyTestableClass.isTestable // true
MyTestableClass.getInfo() // true

如果想要传递参数,可以在装饰器外层再封装一层函数

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

类属性的装饰

当对类属性进行装饰的时候,能够接受三个参数:
target:被修饰的类
name:类成员的名字
descriptor:属性描述符,对象会将这个参数传给Object.defineProperty

首先定义一个readonly装饰器

function readonly(target, name, descriptor){
  descriptor.writable = false; // 将可写属性设为false
  return descriptor;
}

使用readonly装饰类的name方法

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

相当于以下调用

readonly(Person.prototype, 'name', descriptor);

修饰方法

function time(target, name, descriptor) {
    const func = descriptor.value;
    if (typeof func === 'function') {
        // 装饰者模式重写func
        descriptor.value = function(...args) {
            console.log("在头部添加代码", new Date());
            const results = func.apply(this, args);
            console.log("在尾部添加代码", new Date());
            return results;
        }
    }
}
class Person {
    @time
    say() {
        console.log('hello')
    }
}
const person = new Person();
person.say();

如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行

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

外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行

注意

装饰器不能用于修饰函数,因为函数存在变量声明情况

参考文献

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions