JavaScript中的装饰器--Decorator

8,612 阅读6分钟

什么是Decorator

  修饰模式(Decortaor),是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

Decorator的作用

  通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。
  修饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。
  当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些>面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味著要为每一种组合创建一个新类。相反,修饰模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。一个修饰模式的示例是JAVA里的Java I/O Streams的实现。

  上面两段是维基百科中对于Decorator装饰器模式的介绍.简单来说.Decorator就是一种动态地往一个类中添加新的行为的设计模式,它可以在类运行时,扩展一个类的功能.并且去修改类本身的属性和方法.使其可以在不同类之间更灵活的共用一些属性和方法.下面就让我们来看下在ES中Decorator的用法.

Decorator的用法

类本身的修饰

  在ES中Decorator的具体表现形式为:

一个求值结果为函数的表达式,接受目标对象、名称和装饰器描述作为参数,可选地返回一个装饰器描述来安装到目标对象上。

所以说在ES中Decorator的最终本质就是一个函数,这个函数通过接受目标对象的三个参数: 所装饰的类的本身所装饰的类的某个属性的key值所装饰的类的某个属性的描述对象.并通过对这三个参数的操作,已达到为类扩展功能的目的.下面然我们来用具体代码来演示一下:

@eat
class Person {
  constructor() {}
}

function eat(target, key, descriptor) {
  console.log('吃饭');
  console.log(target);
  console.log(key);
  console.log(descriptor);
  target.prototype.act = '我要吃饭';
}

const jack = new Person();
console.log(jack.act);

// 吃饭
// [Function: Person]
// undefined
// undefined
// 我要吃饭

  上面是一个最简单的装饰器的运用.我们首先声明一个类Person,然后在声明一个装饰器函数eat,在eat中将传入的三个参数分别打印出来,并将第一个参数target的原型prototype上添加一个属性act,并赋值为'我要吃饭'.然后将函数eat作为装饰在Person这个类本身上.最后,构造一个Person的实例jack,并打印jack上的act属性.

  然后从下面的运行结果中我们可以看出,代码中会先打印出'吃饭',然后是参数target,其次是参数key,再然后是参数descriptor.最后才是jackact属性.这是因为装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

类的属性的修饰

  看了上面那段代码的运行结果,你可能会用这么一个疑问.Decorator所传进来的三个参数: targetkeydescriptor.为什么只有target有值,而keydescriptor则都是undefined了.事实上这是因为你将装饰器Decorator装饰在类本身上所导致的.在ES中装饰器并不仅仅只能装饰在类本身上,也可以装饰在类的属性上.当装饰在类的属性上时.keydescriptor也就有了用武之地.请看下面这段代码:

class Person {
    constructor() {}

    @test
    name() {console.log('张三');}
}

function test(target, key, descriptor) {
  console.log(target);
  console.log(key);
  console.log(descriptor);
}

const student = new Person();

student.name();

// Person {}
// name
// { value: [Function: name], writable: true, enumerable: false, configurable: true }
// 张三

  在上面代码中我们将test装饰器装饰在Person类的name属性上.然后打印三个传入的入参.分别得到了我们期望的结果.而通过这三个参数,我们就可以对我们要装饰的对象进行一些有趣的修改, 如下面这样:

class Person {

  constructor() {}
    @test
    name() {console.log('张三');}
}

function test(target, key, descriptor) {
  descriptor.value = function () {
    console.log('李四');
  }
}

const student = new Person();

student.name();

// 李四

  在上面代码中,我们给一个类Person的原型上赋值了一个属性name,其值为一个函数,执行时会打印张三两个字.然后我们给属性name装饰了test这个装饰器.在test装饰器中,将传入进来的descriptor对象上的value赋值为一个函数,执行时打印李四两个字.最后构造一个实例student,并执行name方法,执行的结果是打印了李四两个字,这说明通过装饰器,我们完全可以在不改变一个类本身的请况下对一个类的属性进行改写.这使得在不同类中共享同一方法这一操作相当简单,且优雅.需要共享的使用使用装饰器,不需要的时候移除装饰器.完全不用对类的本身进行操作.

  对于装饰器,如果我们感到,固定传入的三个参数不够用的话,我们也可以自行传入参数只需要像下面这么写:


function rename(name) {
  return function(target, key, descriptor) {
    descriptor.value = name;
  }
}

前面已经说过,在ES中**Decorator就是一个求值结果为函数的表达式,**所以,只要你最后的返回结果是一个函数.都是一个合法的装饰器.

Decorator的兼容性

  目前ES中Decorator还处于提案阶段,各大浏览器和node,均未公开支持这一特性.如果想要使用,则需要借用babel的一个插件babel-plugin-transform-decorators才可以.

结束

  上面是我对Decorator的一些见解,希望对大家有所帮助.如果文中任有何不当之处请予以斧正,谢谢

参考资料:

我的个人网址: wangyiming.info