likes
comments
collection
share

TypeScript装饰器实现原理

作者站长头像
站长
· 阅读数 4

费尔明娜,我等待这个机会,已经有51年9个月零4天了,在这段时间里,我一直爱着你,从我第一眼见到你,直到现在,我第一次向你表达我的誓言,我永远爱你,忠贞不渝。———《霍乱时期的爱情》

在学习设计模式的时候看到了一种装饰器模式,随后脑子就联想到了TypeScript中的装饰器,然后通过TypeScript官网提供的在线IDEA工具,我终于懂了TypeScript装饰器的实现原理。

先来看一看在JavaScript中装饰器是如何实现的。

function fun(){
    console.log(1);Ï
}

我想给fun函数在不改变函数本身的情况下扩展一些功能,如果是TypeScript的话就会很容易实现,因为TypeScript中有装饰器,那我们在JavaScript中如何实现呢?。

这里我使用原型链的方式实现一下。

// 前置的before方法
Function.prototype.before = function (fn) {
  var _this = this;

  return function () {
    fn.apply(this, arguments); // 前置callback调用
    return _this.apply(this, arguments); // 返回自身调用
  }

// 后置的after方法
Function.prototype.after = function (fn) {
  var _this = this;

  return function () {
    var ret = _this.apply(this, arguments); // 自身调用
    fn.apply(this, arguments); // 后置callback调用
    return ret;
  }
}

首先看一下beforeafter函数为什么要返回一个函数呢?这是为了在调用的时候实现链式调用,了解jQuery的话应该就知道jQuer就是使用返回函数的方式来实现方法的链式调用。

接下来我们来使用这个装饰器

var resultFn = fun.before(() => {
  console.log(0)
}).after(() => {
  console.log(2);
})

resultFn(); // 0 1 2

通过原型链的方式我们实现了不改变函数本身的情况下进行扩展。

那这样的话TypeScript中的装饰器是否也是这样实现的呢?我带着疑问去看了TypeScript装饰器生成的JavaScript代码。

我们都知道TypeScript装饰器有很多种,我们先来看一下类装饰器。

function RT(target: any) {}

@RT
class Demo {}

上面的TypeScript代码会编译成下面的JavaScript代码

"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 RT(target) {
}
let Demo = class Demo {
};
Demo = __decorate([
  RT
], Demo);

以上代码中最主要的部分就是__decorate函数的实现,那么我们一起来看看__decorate是怎么实现的。

__decorate函数接受了三个参数,分别是:

  • decorators 所有装饰器函数
  • target 目标类
  • key 属性
  • desc 属性的描述

参数个数小于3的话,就是类装饰器,否则就是方法装饰器属性装饰器参数装饰器

那么这个变量r就赋值了Demo

这里的Reflect.decorate是期望以后JavaScript中会支持Reflect.decorate方法。

遍历所有的装饰器函数,调用装饰器函数,如果是类装饰器的话,将r传递并返回。

这样就实现了对类的扩展,其实还是非常简单的,接下来我们来看一下方法装饰器的实现。

function RT(target: any, key: string) { }

class Demo {
  @RT
  sayHello() {
    console.log('sayHello methods');
  }
}

上面的TypeScript代码会编译成下面的JavaScript代码

"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 RT(target, key) { }
class Demo {
    sayHello() {
        console.log('sayHello methods');
    }
}
__decorate([
    RT
], Demo.prototype"sayHello"null);

__decorate方法实现是相同的,不同地方就是参数调用__decorate方法传入的参数个数。

参数个数大于等于3,使用getOwnPropertyDescriptor方法获取属性的描述赋值给desc变量和r变量

遍历所有的装饰器函数,调用装饰器函数,将需要的参数传递,并使用defineProperty定义一个key赋值给target,并返回r

r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r

这个表达式我会以为r的值会是d函数的调用,但是我错了,函数没有返回值的话会返回undefined,那么就是undefined || r,所以r的值还是r

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

这个表达式我也会很懵,调试了一下,结果是这样的

1 && 2 && 3 && 4, 5   // 返回5
1 && false && false, 5 // 返回5

其实逗号之前的表达式我们都可以认为是执行函数的作用或者是赋值的作用,它不起到返回的作用。