Typescript-ts 装饰器源码分析——方法装饰器

Typescript-ts 装饰器源码分析——方法装饰器

Typescript-ts 装饰器源码分析——方法装饰器_第1张图片
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。

方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符

思考一个问题,装饰器是在什么时候被传递进参数的?
为了解决这个疑问,我分两个方面去看,第一个是单个方法装饰器的情况,第二个是多个方法装饰器的情况,其实在了解了单个装饰器的情况之后,多个装饰器的情况就迎刃而解了

在开始之前,我们需要了解ts是如何定义类的,也就是它的面具之下的js是怎么样的

Ts的类

为了方便解释,先写一个简单的类,就叫它Person吧

"use strict";
var Person = /** @class */ (function () {
    function Person() {
    }
    Person.prototype.sayHello = function () {
        console.log("hello");
    };
    return Person;
}());

经过ts编译后的代码:

"use strict";
var Person = /** @class */ (function () {
    function Person() {
    }
    Person.prototype.sayHello = function () {
        console.log("hello");
    };
    return Person;
}());

很明了了吧,其实ts类的本质就是一个立即执行函数,然后在内部声明了一个Person函数,给原型添加方法,最后把它导出来

废话不多说了,来看一下装饰器吧!

单个装饰器

装饰器的作用有很多,比如可以应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义,下面写一个简单的例子,改变方法的定义

function methodDeractor(msg:string):Function{
    return function(target:any, name:string, descriptor:PropertyDescriptor){
        descriptor.value = () => console.log(msg)
    }
}

class Person{
    @methodDeractor("hello world")
    sayHello(){
        console.log("hello")
    }
}

let a = new Person();
a.sayHello(); //hello world

上面的例子是通过属性描述符来修改方法的定义的
methodDeractor内部return的函数才是ts所关心的装饰器,这个装饰器接收三个参数

  • target 目标方法所属类(函数)的原型
  • name 目标方法的名字
  • descriptor 目标方法的属性描述符

这样又回到了开始的问题,那么这些参数是在什么时候被传递的呢?我们来看一下编译过后的js代码:

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function methodDeractor(msg) {
    return function (target, name, descriptor) {
        descriptor.value = function () { return console.log(msg); };
    };
}
var Person = /** @class */ (function () {
    function Person() {
    }
    Person.prototype.sayHello = function () {
        console.log("hello");
    };
    __decorate([
        methodDeractor("hello world")
    ], Person.prototype, "sayHello", null);
    return Person;
}());
var a = new Person();
a.sayHello();

代码底部是类的定义,上面我们已经说过了,ts里的类其实就是一个普通的函数,我们的关注点是下面这些

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

这是装饰器的定义, 也就是代码中的__decorate的定义。我们稍稍把它变成我们容易理解的形式

//装饰器,类的原型对象,方法名,属性描述符
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length,
        //参数个数<3为目标方法,>3为属性描述符
        r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
        //装饰器
        d;

    //检测新特性
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function"){
        r = Reflect.decorate(decorators, target, key, desc);

    }
    //无新特性
    else {
        //遍历装饰器个数
        for(var i = decorators.length - 1; i >= 0; i--){
            if (d = decorators[i]){
                // console.log(d(target, key));
                r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
            }
        }
    }

    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

从头部开始
首先__decorate会检查是否已经存在__decorate,检查的方法是检查this上的__decorator是否存在,在浏览器端,全局的this指代window对象,在node端,this指代module.exports,module.exports的默认值是{},一个空对象,由此判断,这里的__decorate被赋值为后面那个函数。

function (decorators, target, key, desc) {
    var c = arguments.length,
        //参数个数<3为目标方法,>3为属性描述符
        r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
        //装饰器
        d;

    //检测新特性
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function"){
        r = Reflect.decorate(decorators, target, key, desc);

    }
    //无新特性
    else {
        //遍历装饰器个数
        for(var i = decorators.length - 1; i >= 0; i--){
            if (d = decorators[i]){
                // console.log(d(target, key));
                r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
            }
        }
    }

这个函数接收4个参数

  • decorators 其类型为一个数组,表示应用到目标方法的所有装饰器
  • target 其类型为一个对象,是该方法所属类的原型对象
  • key 其类型为字符串,是该方法的名称
  • desc 其类型也为一个对象,是目标方法的属性描述符

在开头,定义了三个变量,c,r,d

  • c为参数的个数,后文通过判断参数的个数来进行传参
  • r为目标方法的属性描述符或该方法所属类的原型对象
  • d为具体的装饰器

下面看一下c,r,d的赋值:
c:

 var c = arguments.length //通过arguments.length直接获取到参数个数

r:

r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc 
//通过划分3这个参数个数来进行分别传值,小于3就是<=2,通常只有装饰器数组和该方法所属类的原型对象被传递进来,此时的r为原型对象,大于3也就是有4个参数,r通过Object.getOwnPropertyDescriptor被赋值为该方法的属性描述符

d:
d没有被明确的赋初始值,在后文,会通过遍历装饰器数组对其进行赋值,现在知道d是一个具体的装饰器就行了

再接着看下一段代码:

//检测新特性
if (typeof Reflect === "object" && typeof Reflect.decorate === "function"){
    r = Reflect.decorate(decorators, target, key, desc);
}

这里是检测是否已经支持新特性了,该新特性是能够支持JS元数据反射的API

如果没有该特性的话直接进入else代码块

 //遍历装饰器个数
for(var i = decorators.length - 1; i >= 0; i--){
    if (d = decorators[i]){
        // console.log(d(target, key));
        r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    }
}

这里做的事情很简单,就是遍历传进来的装饰器个数,然后把遍历出的每个装饰器赋值给d,然后通过传递进来的target,key,r参数对其进行传参执行,此时也是通过判断参数个数来进行的传参,不同个数的参数进行不同的传参。

最后一句,返回目标方法的属性描述符,也就是r

return c > 3 && r && Object.defineProperty(target, key, r), r;

理解完了这段代码,我们再结合起来,综合来看一下,下面两段是:

function methodDeractor(msg) {
    return function (target, name, descriptor) {
        descriptor.value = function () { return console.log(msg); };
    };
}
var Person = /** @class */ (function () {
    function Person() {
    }
    Person.prototype.sayHello = function () {
        console.log("hello");
    };
    __decorate([
        methodDeractor("hello world")
    ], Person.prototype, "sayHello", null);
    return Person;
}());
var a = new Person();
a.sayHello();

开头的methodDeractor是我一开始就声明的装饰器,这里没有做任何的变动,原样的写了下来

下面是我声明的Person类,里面除了给Person附加sayHello方法以外,还做了一件事情,那就是执行装饰器,这里调用了上面声明的__decorate函数,传入四个参数,装饰器列表,该类的原型,目标方法的名称,还有就是目标方法的属性描述符,该属性描述符在此被传递为null,而真正的传值在__decorate内部,也就是r的值。

现在我们回到之前讨论的问题

装饰器是在什么时候被传递进参数的?

答案非常明了了,__decorate调用在A类的定义中,因此,可以说,装饰器是在Person类构造的时候就已经传值了,这也就意味着,装饰器不等Person类new出实例,直接执行,恰恰可以体现装饰器的作用,比如在类的构造阶段为类添加各种元数据进行装饰或者改变目标方法的定义等等。

多个装饰器

上面说了一大堆都是单个装饰器,那多个装饰器呢?

其实多个装饰器和单个装饰器并没有太大的差别,仅仅是decorators扩大了,也就是装饰器数组扩大了,在内部还是得遍历装饰器数组一个一个执行,因此,我们可以得出

就拿文档上的一个例子来说

function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

看一下这个结果

开始在C类的立即执行函数里会执行__decorate函数,f(),g()会被直接传递进去,此时的f,g已经被执行了,因此先打印的应该是:

f(): evaluated
g(): evaluated

在__decorate函数中,通过遍历装饰器列表进行执行,由于遍历的时候是倒序遍历的

for (var i = decorators.length - 1; i >= 0; i--)

因此g装饰器会被先执行

g(): called
f(): called

综上

f(): evaluated
g(): evaluated
g(): called
f(): called

先执行f函数,然后执行g,之后通过倒序遍历执行g装饰器,然后再执行f装饰器

总结

至此,源码分析完了,是不是感觉还是很简单的
装饰器会使我们的代码变得优雅,同一个装饰器可以用于不同的目标,因此提高了复用度,像nest框架,基于express上,用ts写的一套框架,如果你喜欢ts,不妨去试试

你可能感兴趣的:(typescript,优雅的Typescript)