Javascript中装饰器的实现原理

基于Node的web服务器开发中使用decorator对请求进行权限校验和数据格式的处理是一个看起来比较漂亮的写法,这里正好整理一下对javascript中的decorator的理解。javascript

decorator的概念在其余语言中早有存在,在javascript中目前(2017/09/17)还处于stage 2阶段,基本肯定会进入正式的ECMA规范了。可是目前还不能直接使用,只能使用babel来进行语法的转换。html

官方的说法是:java

Decorators make it possible to annotate and modify classes and properties at design time.git

大概的意思就是在运行时改变类或者类的属性。github

正文

首先看下装饰器常见的两种使用:web

  1. 装饰一个类的属性。
function readonly(target, name, descriptor) {
    discriptor.writable = false;
    return discriptor;
}
class Cat {
    @readonly
    say() {
        console.log("meow ~");
    }
}
var kitty = new Cat();
kitty.say = function() {
    console.log("woof !");
}
kitty.say()    // meow ~
复制代码
  1. 装饰一个类。
function isAnimal(target) {
    target.isAnimal = true;
  	return target;
}
@isAnimal
class Cat {
    ...
}
console.log(Cat.isAnimal);    // true
复制代码

装饰一个类的属性

ES6中的类实际上就是一个语法糖,本质上是构造函数,类的属性的定义使用的是 Object.defineProperty() 用一个简单的栗子来理解以下:服务器

class Cat {
    say() {
        console.log("meow ~");
    }
}

function Cat() {}
Object.defineProperty(Cat.prototype, "say", {
    value: function() { console.log("meow ~"); },
    enumerable: false,
    configurable: true,
    writable: true
});
复制代码

细心的小伙伴已经发现了babel

Object.defineProperty(obj, prop, descriptor)
复制代码

接收的参数和做用于类的属性的时候装饰器函数的接收的参数很像。函数

能够知道做用于类的属性的时候的装饰器函数接收的参数就是上述ES6中的类定义属性时候使用Object.defineProperty时接收的参数,如出一辙...ui

本质上也就是说装饰器在做用于类的属性的时候,其实是经过 Object.defineProperty 来对原有的descriptor进行封装:

descriptor:

  • configurable控制是否是能删、能修改descriptor自己。
  • writable控制是否是能修改值。
  • enumerable控制是否是能枚举出属性。
  • value控制对应的值,方法只是一个value是函数的属性。
  • get和set控制访问的读和写逻辑。

经过处理descriptor能够改变原有属性。 被装饰的属性的定义在实际上执行的是如下的代码:

let descriptor = {
    value: function() {
        console.log("meow ~");
    },
    enumerable: false,
    configurable: true,
    writable: true
};
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
Object.defineProperty(Cat.prototype, "say", descriptor);
复制代码

也就是说,上面的那个@readonly其实就是

descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
复制代码

的语法糖,要注意的是,装饰器执行的时间是在属性定义的时候,也就是被装饰的属性在定义后就是已经被装饰器处理过的不同的属性了。

装饰一个类

装饰一个类的时候类自己本质上是一个函数,没有descriptor,target是这个函数自己。

function isAnimal(target) {
    target.isAnimal = true;
  	return target;
}
@isAnimal
class Cat {
    ...
}
console.log(Cat.isAnimal);    // true
复制代码

也就是说,上面的@isAnimal其实就是作了下面这件事

Cat = isAnimal(function Cat() { ... });
复制代码

在了解了两种状况下装饰器本质上作了什么以后,顺带能够看出,装饰器函数执行的时间:

function log(message) {
    return function() {
        console.log(message);
    }
}
console.log('before class');
@log('class Bar')
class Bar {
    @log('class method bar');
    bar() {}
    @log('class getter alice');
    get alice() {}
    @log('class property bob');
    bob = 1;
}
console.log('after class');
let bar = {
    @log('object method bar')
    bar() {}
};
复制代码

输出结果:

before class class method bar class getter alice class property bob class Bar after class object method bar 复制代码

能够看出装饰器在定义时就执行了,也就对应着官方的那句话:

Decorators make it possible to annotate and modify classes and properties at design time.

在类和类的属性定义的时候就对它们进行了"装饰"。

以上大体的说了下javascript的装饰器的原理和使用,可是还有一些细节有待进一步的深刻。

TBD

参考资料

相关文章
相关标签/搜索