TypeScript装饰器原理分析

2023-11-16

1.前言

TypeScript装饰器装饰器是一种特殊类型的声明,它能够被附加到类声明,属性, 访问符,方法或方法参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

2.装饰器原理

2.1 类装饰器

参数:

1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。

function ClassDecorator(target: any) {
    console.log('测试类装饰器');
}

@ClassDecorator
class testClass {
    constructor() {
        console.log('类构造函数')
    }
}

这段代码非常的简单,就是一个类装饰器,我们看来下ts被解析js之后的源码!

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 ClassDecorator(target) {
    console.log('测试类装饰器');
}
var testClass = /** @class */ (function () {
    function testClass() {
        console.log('类构造函数');
    }
    testClass = __decorate([
        ClassDecorator
    ], testClass);
    return testClass;
}());

我们来简单的拆分下源码,分三个部分!

//第一部分 
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 ClassDecorator(target) {
    console.log('测试类装饰器');
}


//第三部分
var testClass = /** @class */ (function () {
    function testClass() {
        console.log('类构造函数');
    }
    testClass = __decorate([
        ClassDecorator
    ], testClass);
    return testClass;
}());

那么先从第一部分开始

//this指向是window,先判断window有没有__decorate,没有的话就定义它
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    //arguments的 要看第三部分调用的地方,__decorate([ClassDecorator], testClass)
    //所以这里的arguments只有2个值,c=2
    var c = arguments.length,
      //2<3,所以这里 r=target,并且声明了一个d变量, d=undefined
        r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,d;
       //这里是es6的语法,判断支持不支持es6的Reflect,并且Reflect.decorate的类型是个函数(据我所以es6的Reflect并没有这个decorate方法)
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else
    //不支持Reflect,所以跑到这个,然后循环这个decorators,还是要看第三部分的调用地方
    //[testClass]是个数组,循环里面的testClass(这里我只写了一个,其实也有可能会有很多个)
    // 根据判断得知   d=testClass,  r=d(r) || r   
    // r=d(r) || r  就是 r=ClassDecorator(testClass) || testClass 
    //意思就是看ClassDecorator有没有返回值,有的话r=这个返回值(ts里面会报错),没有的话r=testClass
        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;
     //这里用到了逗号运算符,最终返回r(没有返回值就返回这个类)
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

第二部分其实就是装饰器,其实实际运行的时候类是它的参数,最后看有没有返回值(第一部分已经解释了)

function ClassDecorator(target) {
    console.log('测试类装饰器');
}

在看第三部分调用的地方

//自执行函数
var testClass = /** @class */ (function () {
    function testClass() {
        console.log('类构造函数');
    }
    //调用__decorate,第一部分已经说了
    testClass = __decorate([
        ClassDecorator
    ], testClass);
    //最后返回的是这个__decorate的返回值r,不懂再看第一部分的解释
    return testClass;
}());

所以类装饰的其实就是每次都执行这个函数

var r=ClassDecorator(testClass) || testClass  // 测试类装饰器
return r;

tips1:第一部分的for循环是 var i = decorators.length - 1; i >= 0; i--,所以当有多个了装饰器时,是倒序执行的!


function ClassDecoratorOne(target: any) {
    console.log('测试类装饰器1');//后执行
}

function ClassDecoratorTwo(target: any) {
    console.log('测试类装饰器2'); //先执行
}

@ClassDecoratorOne
@ClassDecoratorTwo
class testClass {
    constructor() {
        console.log('类构造函数')
    }
}
//测试类装饰器2
//测试类装饰器1

tips2:如果你类装饰器里面强制有返回值(虽然ts语法不允许,会报错,但是解析出来的js还是能执行的),因为上面说了如果没有返回值,就是返回这个类,如果有返回值,将返回这个返回值


function ClassDecoratorOne(target: any) {
    return '我是装饰器的返回值';
}

function ClassDecoratorTwo(target: any) {
    console.log('测试类装饰器2');
}

@ClassDecoratorOne
class testClass {
    constructor() {
        console.log('类构造函数')
    }
}


console.log(testClass); //你把整个类都改了,打印出来是"我是装饰器的返回值"
2.2 属性饰器

参数:

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

function propertyDecorator(propertyValue: string) {
    return function (target: testClass, propertyName: string) {
        target[propertyName] = propertyValue;
    }
}

class testClass {

    constructor() {
        console.log('构造函数')
    }

    @propertyDecorator('zhangsan')
   public test: string;
}
let p = new testClass();
console.log(p.test);

看下解析之后的源码:

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 propertyDecorator(propertyValue) {
    return function (target, propertyName) {
        target[propertyName] = propertyValue;
    };
}
var testClass = /** @class */ (function () {
    function testClass() {
        console.log('构造函数');
    }
    __decorate([
        propertyDecorator('zhangsan')
    ], testClass.prototype, "test", void 0);
    return testClass;
}());
var p = new testClass();
console.log(p.test);

解释一下 void 0是underfined的意思,因为test属性是public,所以第二个参数是类的原型对象,如果是静态属性,第二个参数就是类对象,接下来还是看第一部分

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    //属性装饰器是4个参数,第一个是装饰函数,第二个是类原型对象
    //第三个是装饰的属性,第四个是underfined
    var c = arguments.length, 
    // r=desc=undefined
        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--)
        // r=d(target, key, r) || r
            if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
     // 返回 r 
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

其实最终就是执行下面函数

(function (target, propertyName) {
    target[propertyName] = propertyValue;
})(target, key);

和类装饰不一样一点,这里最后返回值是类函数

2.3 方法装饰器(访问器set.get也属于方法)

参数:

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

function methodDecorator(params: string) {
    return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
        const method = descriptor.value;
        descriptor.value = function (...args: any[]) {
            //转换为字符串
            args = args.map(arg => String(arg));
            return method.apply(this, args)
        };
        return descriptor;
    }
}

class test {
    constructor() {
        console.log('构造函数')
    }

    @methodDecorator('测试方法装饰器')
    getAge(age: number) {
        console.log(age);
    }
}
let p = new test();
p.getAge(18);

下面是解析之后的代码:

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 methodDecorator(params) {
    return function (target, propertyName, descriptor) {
        var method = descriptor.value;
        descriptor.value = function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            //转换为字符串
            args = args.map(function (arg) { return String(arg); });
            return method.apply(this, args);
        };
        return descriptor;
    };
}
var test = /** @class */ (function () {
    function test() {
        console.log('构造函数');
    }
    test.prototype.getAge = function (age) {
        console.log(age);
    };
    __decorate([
        methodDecorator('测试方法装饰器')
    ], test.prototype, "getAge", null);
    return test;
}());
var p = new test();
p.getAge(18);

这里__decorate调用的第4个参数不是undefined了而是null,还是看第一部分,Object.getOwnPropertyDescriptor方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)不懂的话可以点击这里

    //方法装饰器
        var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        //c=4; r={writable: true, enumerable: true, configurable: true, value: f}(getAge 属性描述符对象)
            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 (target, propertyName, descriptor) {
        var method = descriptor.value;
        descriptor.value = function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            //转换为字符串
            args = args.map(function (arg) { return String(arg); });
            return method.apply(this, args);
        };
        return descriptor;
    })(test.prototype, "getAge",{writable: true, enumerable: true, configurable: true, value: f})

这个函数里面其实是个闭包,把test.prototype.getAge的value作为method变量先保存起来,然后再改了value的值,当调用test.prototype.getAge方法的时候可以将调用的参数转变为字符串,然后再去执行之前保存好的method 方法!

2.4 参数装饰器

参数:

1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2. 属性的名称。
3.参数的index的值

function paramsDecorator(target: any, propertyName: string, index: number) {
    var key = propertyName + "index"
    target[key] = index;
}


class test {
    constructor() {
        console.log('构造函数')
    }

    public getAge(@paramsDecorator age: number) {
        console.log(age);
    }
}
let p = new test();
p.getAge(18);

下面是解析之后的代码:

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;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
function paramsDecorator(target, propertyName, index) {
    var key = propertyName + "index";
    target[key] = index;
}
var test = /** @class */ (function () {
    function test() {
        console.log('构造函数');
    }
    test.prototype.getAge = function (age) {
        console.log(age);
    };
    __decorate([
        __param(0, paramsDecorator)
    ], test.prototype, "getAge", null);
    return test;
}());
var p = new test();
p.getAge(18);

首先会先调用 __param(0, paramsDecorator),

var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
__param(0, paramsDecorator);

又是返回一个闭包函数,如下:

// paramIndex = 0;
function (target, key) { decorator(target, key, paramIndex)}

第一部分最后可以简化为

//r 为属性描述符对象, d为上面返回的闭包函数,target为类原型对象,key为属性名
r=d(target, key, r) || r
return r

最后执行上面的闭包函数

3.装饰器执行顺序

先看段代码:

function paramsDecorator(target: any, propertyName: string, index: number) {
    console.log("参数装饰器")
}

function methodDecorator(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    console.log("方法装饰器")
}


function ClassDecoratorOne(target: any) {
    console.log('类装饰器');
}

function propertyDecorator(target: test, propertyName: string) {
    console.log("属性装饰器")
}


function enumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("访问器装饰器")

}


@ClassDecoratorOne
class test {
    constructor() {

    }

    @methodDecorator //方法装饰器
    //参数装饰器
    public getAge(@paramsDecorator  value: number) {

    }

    @propertyDecorator //属性装饰器
    public age: string
    public name: string

    @enumerable   //访问装饰器
    get getName() {
        return this.name;
    }
}
let p = new test();
p.getAge(18);

执行结果如下:
在这里插入图片描述
如果把方法,属性,访问器装饰器位置调整下,访问器在最上面,方法装饰器在中间,最后放属性装饰器呢,看下面代码:

function paramsDecorator(target: any, propertyName: string, index: number) {
    console.log("参数装饰器")
}

function paramsDecoratorConstructor(target: any, propertyName: string, index: number) {
    console.log("构造参数装饰器")
}

function methodDecorator(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    console.log("方法装饰器")
}


function ClassDecoratorOne(target: any) {
    console.log('类装饰器');
}

function propertyDecorator(target: test, propertyName: string) {
    console.log("属性装饰器")
}


function enumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("访问器装饰器")

}


@ClassDecoratorOne
class test {

    constructor(@paramsDecoratorConstructor name:string) {
         this.name  = value
        console.log("构造函数")
    }   

    @enumerable   //访问装饰器
    get getName() {
        return this.name; 
    }

    @methodDecorator //方法装饰器
    //参数装饰器
    public getAge(@paramsDecorator value: number) {

    }

    @propertyDecorator //属性装饰器
    public age: string
    public name: string


}
let p = new test('张三');
p.getAge(18);

执行结果如下:
在这里插入图片描述

所以可以得出结论:不管怎么样,有参数的就先执行参数装饰器(类装饰器也如此,先执行类参数装饰器,再执行类装饰器),访问器,方法,属性装饰器,谁写在前面谁先执行,看代码的先后顺序,然后再执行构造函数参数装饰器(有的话),最后是类装饰器!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TypeScript装饰器原理分析 的相关文章

随机推荐

  • 【FPGA多周期约束】

    多周期约束及语法 一 什么时候需要用到多周期约束 Vivado TimeQuest等时序引擎默认是按照单周期关系分析数据关系的 即数据在发起沿发送 在捕获被捕获 发起沿和捕获沿相差一个周期 但是很多情况是 数据路径逻辑较为复杂 导致延时较大
  • 朴素贝叶斯基本原理和预测过程、先验概率、后验概率、似然概率概念

    贝叶斯原理是英国数学家托马斯 贝叶斯提出的 贝叶斯原理 建立在主观判断的基础上 在我们不了解所有客观事实的情况下 同样可以先估计一个值 然后根据实际结果不断进行修正 举例 一个袋子里有10个球 其中6个黑球 4个白球 那么随机抓一个黑球的概
  • 关于电商秒杀系统中防超卖、以及高性能下单的处理方案简述

    秒杀抢购系统的成功平稳运行 有一些需要注意的知识点 1 高并发 以及刷接口等黑客请求对服务端的负载冲击 2 高并发时带来的超卖 即商品数量的控制 3 高负载下 下单的速度和成功率的保证 4 其他 以秒杀单品为例 如抢小米手机 解决方案探讨
  • 大型网站架构核心要素之可用性:高可用架构

    前言 上节我们讲了网站核心要素之性能 这节我们接着讲第二个核心要素可用性 网站的可用性 描述的是一个网站是否可以正常使用的特性 这个特性是比较关键的 直接影响公司形象和利益 因此也有很多大公司把这点作为技术人员的绩效考核之一 既然那么重要
  • Springboot毕设项目地铁站自动售票系统77x9w(java+VUE+Mybatis+Maven+Mysql)

    项目运行 环境配置 Jdk1 8 Tomcat8 5 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 Springboot myb
  • Plotly Express 详细使用指南,20组案例从入门到进阶(附源代码)

    作者 阳哥 出品 Python数据之道 ID PyDataLab 大家好 我是阳哥 今天跟大家分享的是 Plotly Express 的详细使用教程 Plotly Express 是 Python 交互式可视化库 Plotly 的高级组件
  • 【Deepin-15.11】下【Datax】使用【插件】进行【csv文件读写】

    接上 1 将Downloads目录下的压缩包放到指定文件夹下 题目要求 2 按照要求创建文件夹 题目要求 3 Github Datax txtfilereader模板官网将模板copy下来 写入文本文档并修改后缀名 复制到job 题目指定
  • Android Studio 中如何添加ViewModelProviders依赖?

    我的做法是在class类中直接导入文件 import androidx lifecycle ViewModelProvidels 一个小白 就当是记录一下啦
  • 86-信号和槽-信号与槽的参数

    信号与槽的参数 上节介绍了信号与槽的基本使用方法 本节介绍其参数传递的情况 通过为槽函数传递特定的参数 可以实现更复杂的功能 既可以传递 Qt 的内置参数 也可以传递自定义参数 当然 内置参数和自定义参数也可以放在一起传递 自定义参数既可以
  • 不习惯的 Vue3 起步六 の Echarts绘制下钻地图

    序 看过一些可视化大屏展示地图的例子 准备动手做做 既然要开始比制作 那么先把目标定好 做一个展示中国城市的下钻地图 使用 Vue3 Vite Typescript echarts 实现效果 准备工作 创建项目 因为准备使用Vue3 Vit
  • Vue——自定义指令

    自定义全局指令 注 使用指令时必须在指名名称前加前缀v 即v 指令名称 Vue directive hello bind 常用 alert 指令第一次绑定到元素上时调用 只调用一次 可执行初始化操作 inserted alert 被绑定元素
  • 【上位机】通过QTCreator编写WIFI上位机与网络调试助手通信绘制曲线

    文章目录 前言 一 使用QT Creator编写上位机 二 上位机与网络调试助手联调 三 总结 前言 17年电赛H题中要求编写WIFI上位机实现远程幅频特性曲线显示 以下是本人在近期摸索出来的一些心得及体会 一 使用QT Creator编写
  • 目前有哪些好用的测试管理工具?

    PingCode Testhub Zephyr for jira 禅道等都是当下不错的测试管理工具 其实就测试用例管理工具或Bug管理工具来说 当前市场上种类并不少 功能也各有特色 我们在工具选型过程中最大的问题并不是不知道有哪些好的工具
  • FastDFS单机部署安装

    FastDFS单机部署安装 文章目录 FastDFS单机部署安装 前言 1 服务器规划 2 安装包 3 所有tracker和storage节点都执行如下操作 3 1 安装所需的依赖包 3 2 安装libfatscommon 3 3 安装Fa
  • mac电脑屏幕录制Berrycast Mac屏幕录制软件

    Berrycast是一款为Mac设计的优秀屏幕录制软件 它让屏幕录制变得简单而高效 以下是Berrycast的一些主要特点 简单的用户界面 Berrycast拥有直观和简洁的用户界面 使得用户可以轻松上手 高质量的视频输出 Berrycas
  • Vue2开发插件并发布到npm

    Vue3 TS Vite开发插件并发布到npm 目标 创建vue amazing selector下拉框组件 并发布到npm 效果如下图 默认时样式 禁用时样式 创建vue项目 vue create vue amazing selector
  • 指针和引用的区别

    从概念上讲 指针从本质上讲就是存放变量地址的一个变量 在逻辑上是独立的 它可以被改变 包括其所指向的地址的改变和其指向的地址中所存放的数据的改变 而引用是一个别名 它在逻辑上不是独立的 它的存在具有依附性 所以引用必须在一开始就被初始化 而
  • Kafka传输数据到Spark Streaming通过编写程序java、scala程序实现操作

    一 案例说明 现有一电商网站数据文件 名为buyer favorite1 记录了用户对商品的收藏数据 数据以 t 键分割 数据内容及数据格式如下 二 前置准备工作 项目环境说明 Linux Ubuntu 16 04 jdk 7u75 lin
  • segment anything原来可以这么玩

    Segment Anything能给我们做什么 前言 内容 具体实现 成果 前言 最近 大模型的热度确实是非常非常的高 从chatgpt到segment anything 这些东西整的我这刚入门的小白确实有点懵逼 最近实在是不知道干啥 想想
  • TypeScript装饰器原理分析

    文章目录 1 前言 2 装饰器原理 2 1 类装饰器 2 2 属性饰器 2 3 方法装饰器 访问器set get也属于方法 2 4 参数装饰器 3 装饰器执行顺序 1 前言 TypeScript装饰器装饰器是一种特殊类型的声明 它能够被附加