Javascript中装饰器的实现原理

Javascript中装饰器的实现原理

技术杂谈小彩虹2021-07-09 0:39:4490A+A-

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

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

官方的说法是:

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

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

正文

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

  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
});

细心的小伙伴已经发现了

Object.defineProperty(obj, prop, descriptor)

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

可以知道作用于类的属性的时候的装饰器函数接收的参数就是上述ES6中的类定义属性时候使用Object.defineProperty时接收的参数,一模一样...

本质上也就是说装饰器在作用于类的属性的时候,实际上是通过 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

参考资料

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们