TypeScript装饰器Decorators(装饰类、类方法(静态方法、成员方法、set/get方法)、属性)

目录

Decorators

使用

Ts在线运行平台

装饰类

带运行括号

多个带运行符装饰器运行顺序

不带运行括号

多个装饰时运行顺序

多个装饰器装饰类

常见用法

装饰类方法

装饰类成员方法(非静态方法)

带运行符

多个装饰函数

不带运行符

装饰类静态成员方法

装饰类set、get方法

属性装饰器

方法参数装饰器

不同装饰器调用顺序


Decorators

修饰类和类中的成员方法、变量,可以重写或修改修饰的内容。

使用

1.安装编译器,安装后通过tsx 文件名,可以将ts文件转译为js文件

 npm install -g typescript

 2.安转运行环境,安装后可以直接通过ts-node 文件名直接运行ts文件。

npm install -g ts-node

3.方法一:命令行生成tsconfig.json文件,并打开注释项中的experimentalDecorators。

tsc --init

方法二:自己新建文件夹tsconfig.json。

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

4.命令行中通过ts-node “执行文件”即可运行带有装饰器的ts文件。

Ts在线运行平台

TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript

装饰类

带运行括号

运行装饰函数的内容且该函数的返回的内容也会作为函数直接被执行

该函数的返回函数中可以接受一个系统参数为被装饰类本身,通常用来修改类的静态变量、静态函数。

可以给装饰函数设置参数由装饰时传入,也可以不设置参数。

function marker(n: number) {
    console.log("装饰器工厂 marker 被调用,参数是 " + n);
    return function (target: any) {// target为被装饰的类本身
        console.log("装饰器函数被调用,其标记的数字为 " + n);
        target.index = n;
    };
}
@marker(3)
class Counter {
    static index: number;
}
console.log(Counter.index);
//装饰器工厂 marker 被调用,参数是 3
//装饰器函数被调用,其标记的数字为 3
//3

需要在修饰函数的返回函数中接收系统的参数(被装饰器修饰的类)

function breaker() {
    console.log("执行了");
    return function (target: any) {
        target.index = 4;
    };
}
@breaker()
class Counter {
    static index: number;
}
console.log(Counter.index)
//执行了
//4

多个带运行符装饰器运行顺序

  1. 从装饰顺序从上到下运行装饰函数内容(不包括装饰函数的返回内容)
  2. 从装饰顺序从下到上运行装饰函数中的返回内容
function marker(n: number) {
    console.log("装饰器工厂 marker 被调用,参数是 " + n);
    return function (target: any) {
        console.log("装饰器函数被调用,其标记的数字为 " + n);
        target.index = n;
    };
}
@marker(1)
@marker(2)
@marker(3)
class Counter {
    static index: number;
    public a: number = 1;
}
console.log(Counter.index)
//装饰器工厂 marker 被调用,参数是 1
//装饰器工厂 marker 被调用,参数是 2
//装饰器工厂 marker 被调用,参数是 3
//装饰器函数被调用,其标记的数字为 3
//装饰器函数被调用,其标记的数字为 2
//装饰器函数被调用,其标记的数字为 1
//1

不带运行括号

会运行装饰函数,该函数的返回内容不会被执行,在修饰函数中接收系统传的被装饰类作为参数,返回的内容不会被执行,会替换修饰的类。

function breaker(target: any):any {// target为被装饰的类本身
    console.log("执行了1",target);
    return function (a: any) {
        console.log("执行了3");
        a.index = 4;
    };
}
@breaker
class Counter {
    static index: number;
}
console.log("执行了2");
let a = {};
(Counter as unknown as (b:any)=>{})(a);
console.log((a as any).index)
//执行了1 [Function: Counter]
//执行了2
//执行了3
//4

多个装饰时运行顺序

  1. 按装饰顺序从下到上运行只运行装饰函数内容(不包括返回内容),上一个的装饰函数的返回内容可以在下一个装饰函数的入参中取到。
  2. 最后一个装饰函数的返回内容会替换类本身
function substitute(target: any):any {
    console.log("target为breaker中返回的函数:", target); // 输出被装饰的类本身
    return class Substitute {
        constructor() {
            console.log("Hello Decorator");
        }
    };
}
function breaker(target: any):any {
    console.log("我是来破坏队形的",target);
    return function a (target: any) {
        console.log("装饰器函数被调用,其标记的数字为4");
        target.index = 4;
    };
}
@substitute
@breaker
class Counter {
    static index: number;
    public a: number = 1;
}
new Counter()
//我是来破坏队形的 [Function: Counter]
//target为breaker中返回的函数: [Function: a]
//Hello Decorator

多个装饰器装饰类

  1. 从装饰顺序从上到下运行带运算符的装饰函数内容(不带返回内容)
  2. 从装饰顺序从下到上运行(带运算符的装饰函数返回内容)和(不带运算符的装饰函数内容)
  3. 在2过程中,当运行到不带运算符的装饰函数时,会将类改变为该函数的返回内容,后面的装饰函数拿到的类会是上一个不带运算符的装饰函数的返回内容而不在是装饰类本身。
function marker(n: number) {
    console.log("装饰器工厂 marker 被调用,参数是 " + n);
    return function (target: any) {
        console.log("装饰器函数被调用,其标记的数字为 " + n);
        target.index = n;
    };
}
function substitute(target: any): any {
    console.log("target为breaker中返回的函数:", target); // 输出被装饰的类本身
    return class Substitute {
        constructor() {
            console.log("Hello Decorator");
        }
    };
}
function breaker(t: any): any {
    console.log("我是来破坏队形的", t);
    return function a(target: any) {
        console.log("装饰器函数被调用,其标记的数字为4");
        target.index = 4;
    };
}
@substitute
@marker(1)
@marker(2)
@breaker
@marker(3)
class Counter {
    static index: number;
}
new Counter()
//装饰器工厂 marker 被调用,参数是 1
//装饰器工厂 marker 被调用,参数是 2
//装饰器工厂 marker 被调用,参数是 3
//装饰器函数被调用,其标记的数字为 3
//我是来破坏队形的 [Function: Counter] { index: 3 }
//装饰器函数被调用,其标记的数字为 2
//装饰器函数被调用,其标记的数字为 1
//target为breaker中返回的函数: [Function: a] { index: 1 }
//Hello Decorator

常见用法

用作重写构造方法,通过类的继承实现。

function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    // <T extends new(...args: any[]) => {}>
    console.log(constructor)
    return class a extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}
@classDecorator
class Greeter {
    property = "111";
    hello: string;
    static __proto__: any;
    constructor(m: string) {
        this.hello = m;
    }
    m(){
        console.log(this.hello)
    }
}
console.log(new Greeter.__proto__("12"));
console.log(new Greeter("1"));
//[Function: Greeter]
//Greeter { property: '111', hello: '12' }
//a { property: '111', hello: 'override', newProperty: 'new property' }

装饰类方法

用在类的prototype上添加属性、方法,重写装饰方法。。

注意:代码输出的目标版本小于ES5,属性描述符会是undefined。

装饰类成员方法(非静态方法)

带运行符

先运行装饰函数内容,装饰函数可以接受运行符的参数,再运行装饰函数的返回的函数,其中返回的函数会接受修饰类成员函数的信息。可以接受3个参数

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象(下面示例为类的原型对象)。
  2. 成员方法的名字。
  3. 成员的属性描述符
function enumerable(value: boolean) {
    console.log("value:",value)
    // 当前修饰为实例方法,target为类的原型,target= Test.prototype;propertyKey为修饰的成员方法名;descriptor为修饰方法的相关信息;
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(descriptor)
        // {
        //     value: [Function (anonymous)],
        //     writable: true,
        //     enumerable: false,//成员方法默认是不可枚举的
        //     configurable: true
        // }
        descriptor.enumerable = value;//修改成员方法为可枚举的
        console.log(descriptor.value == target.greet)
        // value为修饰的greet函数,所以返回true
        target.greet()// target为Test.prototype 
        target.a = 'bbb'
        target.greet = function () {
            console.log("我改变了原型上的成员函数")
        }
        target = {
            greet() {
                console.log(22233)
            }
        }//这里给target重新赋值无效
        console.log(propertyKey)
        //为修饰的成员属性名greet
    };
}
class Test {
    greeting: string;
    a = 'aaa';
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        console.log("我是被装饰器修饰的成员函数" + this.greeting);
    }
}
let test = new Test("aaa")
console.log(test.a)//类中的属性a在构建的时候被赋值,打印为 aaa
//@ts-ignore
console.log(test.__proto__.a)//这里为原型上的属性a,在装饰函数中被赋值了,为 bbb
//@ts-ignore
test.greet()
//@ts-ignore
test.__proto__.greet()
//@ts-ignore
console.log(test.greet==test.__proto__.greet)
// value: false
// {
//   value: [Function (anonymous)],
//   writable: true,
//   enumerable: true,
//   configurable: true
// }
// true
// 我是被装饰器修饰的成员函数undefined
// greet
// aaa
// bbb
// 我是被装饰器修饰的成员函数aaa
// 我是被装饰器修饰的成员函数undefined
// true

注意target为类的原型,对它进行重新赋值引用无效,可以修改它的属性,但是如果和类中的方法名重名了会被类中的方法名覆盖。(例如示例中的装饰函数修改了greet无效,被类中的同名方法覆盖了)

多个装饰函数

和装饰类时运行顺序相同,先从上到下运行装饰函数,再从下到上运行装饰函数的返回内容,其中每次拿到的原型对象以及属性描述符是基于上一次修改后的。

function enumerable(value: number) {
    console.log(value)
    // 当前修饰为实例方法,target为类的原型,target= Greeter.prototype;propertyKey为修饰的成员方法名;descriptor为修饰方法的相关信息;
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(target.a) //第一次为undefined,第二次为一次赋值的aaa
        target.a = 'aaa'
        target.greet()// 第一次为类中的函数,第二次为一次赋值的函数
        target.greet = function () {
            console.log("我尝试改变类的成员函数")
        }
    };
}
class Greeter {
    greeting: string;
    a: any;
    b = 345;
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(1)
    @enumerable(2)
    greet() {
        console.log("我是被装饰器修饰的成员函数" + this.greeting);
    }
}
Greeter2.prototype.greet() //由于类中的函数会覆盖装饰函数修改后的内容,所以最后运行还是类中的函数,
// 1
// 2
// undefined
// 我是被装饰器修饰的成员函数undefined
// aaa
// 我尝试改变类的成员函数
// 我是被装饰器修饰的成员函数undefined

不带运行符

和装饰类的时候不同,这个装饰函数的return值没有意义。装饰函数直接接收3个参数,

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

2.成员方法的名字。

3.成员的属性描述符

function enumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(target)
    console.log(propertyKey)
    console.log(descriptor)
}

class Greeter {
    greeting: string;
    a: any;
    b = 345;
    constructor(message: string) {
        this.greeting = message;
    }
    @enumerable
    greet() {
        console.log("我是被装饰器修饰的静态函数" );
    }
}
// { greet: [Function (anonymous)] }
// greet
// {
//   value: [Function (anonymous)],
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

装饰类静态成员方法

装饰函数的返回的函数接受修饰类成员函数的信息。可以接受3个参数

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象(下面示例为类的构照函数)。
  2. 成员方法的名字。
  3. 成员的属性描述符
function enumerable() {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        target.prototype.a = 1
        // 修饰的是静态成员,所以这里的target为类的构造函数 这里
        // Greeter.prototype.constructor === Greeter 所以在target.prototype上赋值属性为a,下面通过实例对象可以拿到原型上的a属性。
        console.log(target)
    }
}

class Greeter {
    greeting: string;
    a: any;
    constructor(message: string) {
        this.greeting = message;
    }
    @enumerable()
    static greet() {
        console.log("我是被装饰器修饰的静态函数");
    }
}
console.log(new Greeter('a').a)
// [Function: Greeter] { greet: [Function (anonymous)] }
// 1

装饰类set、get方法

装饰函数的返回的函数接受修饰类成员函数的信息。可以接受3个参数

  1. 对类的原型对象。
  2. 成员方法的名字。
  3. 成员的属性描述符

注意:

1.TypeScript 不允许同时装饰一个成员的get和set访问器,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。

2.装饰set、get属性函数时,descriptor上会多了set、get属性,通过这些属性可以拿到装饰的set、get属性函数,可以做重写操作。

function aaa(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(descriptor)
    descriptor.set = (value: string) => {//set方法被装饰函数重写了
        console.log('执行了')
        target.myName = value; // 这里target为Person.prototype
    }
}
class Person {
    private _name: string;
    constructor(name: string) {
        this._name = name;
    }
    @aaa
    get name(): string {
        return this._name;
    }
    // @aaa  不允许同时装饰一个成员的get和set访问器,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。
    set name(value: string) {
        this._name = value;
    }
}
let p = new Person('yangbuyiya');
p.name = 'zs';//这里设置的p.name会设置在Person.prototype上
console.log(p.name);
// @ts-ignore
console.log(p.__proto__.myName);
//  {
//     get: [Function: get],
//     set: [Function: set],
//     enumerable: false,
//     configurable: true
//   }
//   执行了
//   yangbuyiya
//   zs

属性装饰器

装饰函数的返回的函数接受修饰类属性的信息。可以接受2个参数

  1. 对于静态属性来说是类的构造函数,对于实例属性是类的原型对象(下面示例为类的原型对象)。
  2. 成员属性的名字。

用于在原型上添加属性。

function logProperty(params: any) {
    console.log(params)
    return function (target: any, attr: any) {
        console.log(target) // {constructor: ƒ, getData: ƒ}
        console.log(attr) // apiUrl
        target[attr] = params
    }
}
class HttpRequest {
    @logProperty('http://www.baidu.com')
    public apiUrl: string | undefined;
    constructor() { }
    getData() {
        console.log(this.apiUrl);
    }
}
let http: any = new HttpRequest();
console.log(http.__proto__);
// http://www.baidu.com
// { getData: [Function (anonymous)] }
// apiUrl
// { getData: [Function (anonymous)], apiUrl: 'http://www.baidu.com' }

方法参数装饰器

装饰函数的返回的函数接受修饰类方法参数的信息。可以接受3个参数

  1. 对于静态方法参数来说是类的构造函数,对于实例方法参数是类的原型对象(下面示例为类的构造函数)。
  2. 方法名称。
  3. 修饰参数在方法中的索引(从0开始)
function logParams (params:any) {
    return function (target: any, methodsName: any, paramsIndex: any) {
        console.log(target)
        console.log(methodsName)
        console.log(paramsIndex)
        target.prototype.apiUrl = params
    }
}

class HttpRequest {
    public url: string | undefined;
    constructor() {
    }
    getParams() {}
    static getData(name:string, @logParams('www.demo.com') uuid:any) {
        console.log('我是静态方法')
    }
}
let http:any = new HttpRequest();
HttpRequest.getData('12123',123123);
console.log(http.__proto__.apiUrl);
// [Function: HttpRequest] { getData: [Function (anonymous)] }
// getData
// 1
// 我是静态方法
// www.demo.com

不同装饰器调用顺序

类中不同声明上的装饰器将按以下规定的顺序应用:

  1. 参数装饰器,然后依次是方法装饰器访问符装饰器,或属性装饰器应用到每个实例成员。
  2. 参数装饰器,然后依次是方法装饰器访问符装饰器,或属性装饰器应用到每个静态成员。
  3. 参数装饰器应用到构造函数。
  4. 类装饰器应用到类。
  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
除了使用反射机制以外,TypeScript 还提供了其他一些方法来获取的所有属性方法。下面是两种常用的方法: 1. 使用 `Object.keys()` 函数来获取的所有属性名,然后再通过属性名获取对应的值。 ```typescript class MyClass { private privateProperty: string; public publicProperty: number; constructor() { this.privateProperty = 'private property'; this.publicProperty = 123; } private privateMethod() { console.log('private method'); } public publicMethod() { console.log('public method'); } } const myInstance = new MyClass(); // 获取的所有属性 const properties = Object.keys(myInstance); console.log(properties); // ["privateProperty", "publicProperty"] // 获取的所有方法 const methods = Object.keys(MyClass.prototype).filter((property) => typeof myInstance[property] === 'function'); console.log(methods); // ["privateMethod", "publicMethod"] ``` 2. 使用 `Reflect.ownKeys()` 函数来获取的所有属性方法名称。 ```typescript class MyClass { private privateProperty: string; public publicProperty: number; constructor() { this.privateProperty = 'private property'; this.publicProperty = 123; } private privateMethod() { console.log('private method'); } public publicMethod() { console.log('public method'); } } const myInstance = new MyClass(); // 获取的所有属性方法 const propertiesAndMethods = Reflect.ownKeys(myInstance); console.log(propertiesAndMethods); // ["privateProperty", "publicProperty", "privateMethod", "publicMethod"] ``` 这两种方法都可以获取的所有属性方法,你可以根据自己的需求选择其中一种。需要注意的是,这些方法只能获取到实例属性方法,无法获取静态属性方法。如果你需要获取静态成员,你可以通过本身直接访问。例如,`MyClass.staticProperty` 和 `MyClass.staticMethod`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值