《TypeScript》入门与精通-浅谈高级语法装饰器(Decorators)

@UserConfig

class User {

constructor() {

console.log(“你好”);

}

}

new User();

就这么简单,从中可以看到,装饰器的语法是一个@符后面跟着的是装饰器函数,装饰器函数有一个默认参数,它的这个默认参数就是待装饰的构造函数

既然参数是构造函数,那么,我们就可以给其附加一些操作和属性,比如:

function UserConfig(constructor: any) {

constructor.prototype.name = () => {

console.log(“oliver”);

};

}

@UserConfig

class User {

constructor() {

console.log(“你好”);

}

}

const user = new User();

(user as any).name();

运行代码,打印出了:oliver,可以看出,通过装饰器,我们给User附加了一个name的方法,这个方法可以打印出名字,这里注意了,装饰器生效的实际并不是在实例化的过程中,而是在定义类的时候就已经生效了,比如:

function UserConfig(constructor: any) {

constructor.prototype.name = () => {

console.log(“oliver”);

};

}

@UserConfig

class User {

constructor() {

// console.log(1);

}

}

即使是这样,这个console.log还是会执行的,因为装饰器的生效时机是在类声明的时候,注意:类装饰器是所有装饰器中最晚执行的

到这里可能有同学会问,装饰器能加参数吗,比如,我们需要通过参数来判断装饰器需不需要生效,小伙子,灵活点嘛,既然只要后面跟着的是一个函数,那么我们把函数作为返回值,不就可以接收参数了,只要能接收参数,那么里面自然就可以加逻辑,加判断,具体例子如下:

function UserConfig(params: string) {

return function(constructor: any) {

constructor.prototype.name = () => {

console.log(params);

};

};

}

@UserConfig(“oliver”)

class User {

constructor() {

console.log(“你好”);

}

}

const user = new User();

(user as any).name();

到这里,相信各位小伙伴对装饰器有一定了解了吧,现在我们来看一个复杂一点的例子

// 定义一个接口,约束类

interface UserInterface {

name: string;

}

// 定义一个装饰器

function UserConfig<T extends new (…arg: any[]) => any>(constructor: T) {

return class extends constructor {

name = “lilei”;

};

}

// 定义类并使用装饰器

@UserConfig

class User implements UserInterface {

name: string;

constructor(name: string) {

this.name = name;

}

}

const user = new User(“oliver”);

console.log(user.name);

解释一下这段代码:

1)最上面1-4行,定义了一个接口;

2)6-11行,定义了一个装饰器,它的类型比较奇怪,是一个泛型,泛型T,继承了一个new (…arg: any[]) => any这个东西,然后,就是它有一个参数,参数的类型也是泛型T,这个装饰器函数返回了一个类,并且这个类继承了我们的传入的构造函数,这也就是一旦使用我们的装饰器去装饰了某个类,那么这个类上面所有的属性,方法都会被继承,到这里应该没什么问题吧,并在在这个装饰器内部,将其name属性的值改写为了lilei;

那么这里的难点就剩下:new (…arg: any[]) => any,这个是啥,通过关键字new,我们知道这是一个构造函数,并且后面这个也应该是一个函数,…arg: any[]这其实是一个参数的简写,代表着这个构造函数接收任意多的参数,之后这些参数被转成了数组的形式,并且因为我们不知道这个数组的每一项究竟是什么,因此这个数组的类型就是由any组成的数组;

综合的说,new (…arg: any[]) => any,这段代码就是它的类型是一个构造函数,这个构造函数的参数是任意多且任意类型,并且返回值也是任意类型;

3)14行,使用了装饰器

4)15-20行,定义了一个类,它接受接口UserInterface的约束,并且它有一个属性,属性的key是name,值是实例化的时候传入的;

5)21行,实例化了一个user,传入的值是oliver;

6)22行,打印name值

通过打印,结果值是lilei,可以理解吧,因为装饰器将其的值改写了;

方法装饰器


正如上面所说,装饰器是一个函数,既然类装饰器装饰的是类,方法装饰器自然装饰的是方法,看个例子先:

interface UserInterface {

name: string;

getName: () => string;

}

function nameConfig(state: boolean = false) {

return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

console.log(target, propertyKey);

};

}

class User implements UserInterface {

name: string;

constructor(name: string) {

this.name = name;

}

@nameConfig()

getName() {

return this.name;

}

}

如示例,方法装饰器就是将装饰器放在方法上,同样,方法装饰器执行的时机是在方法被定义的时候,而不是被实例化的时候,另外方法装饰器的执行是在类装饰器之前的,如上面这段代码所示,运行的时候会直接执行console.log;

通过例子知道,方法装饰器一共有三个参数,分别是target,propertyKey,descriptor,我们分别解释一下:

  1. propertyKey,这个就是方法名,例子中就是getName这个方法名;

  2. target,这个其实要看具体情况,看这个方法是不是静态方法,也就是这个方法带不带static,看个例子

function nameConfig(state: boolean = false) {

return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

console.log(target);

};

}

class User implements UserInterface {

name: string;

constructor(name: string) {

this.name = name;

}

// target对应的是类的prototype

@nameConfig()

getName() {

return this.name;

}

// target对应的是类的构造函数

@nameConfig()

static getName() {

return this.name;

}

}

在这里插入图片描述

  1. descriptor,这个东西就比较复杂了,它和Object.defineProperty()的第三个参数是一样的,具体可以看MDN上的这个解释:Object.defineProperty(),简单的说,就是用来定义或修改属性描述符的,看一个简单的例子吧:

interface UserInterface {

name: string;

getName: () => string;

}

function nameConfig(state: boolean = false) {

return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

// 将这个writable这个属性描述符设置成false,代表不可以重写个方法

descriptor.writable = false;

};

}

class User implements UserInterface {

name: string;

constructor(name: string) {

this.name = name;

}

@nameConfig()

getName() {

return this.name;

}

@nameConfig()

static getName() {

return this.name;

}

}

const user = new User(“oliver”);

// 重写getName方法

user.getName = () => {

return “new Oliver”;

};

console.log(user.getName());

我们知道,方法是可以重写的,如果我们不开放重写方法的功能,那么我们就可以将writeable这个属性描述符设置成false,代表不可以重写;

访问器装饰器


直接看例子吧,用法和执行时机和类装饰器、方法装饰器一致

interface UserInterface {

name: string;

}

function nameConfig(state: boolean = false) {

return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

// 不可修改

descriptor.writable = false;

};

}

class User implements UserInterface {

constructor(private _name: string) {}

get name() {

return this._name;

}

@nameConfig()

set name(name: string) {

this._name = name;

}

}

const user = new User(“oliver”);

// 修改name属性

user.name = “new oliver”;

console.log(user.name);

你没看错,用法和方法装饰器几乎一摸一样,在例子中,我们同样设置了writeable为不可编辑,那么修改直接会报错;

属性装饰器


也同样直接看例子吧,用法和执行时机和类装饰器、方法装饰器一致另外值得注意的是,属性装饰器的是最先执行的,它执行的比方法装饰器和类装饰器这些要早,区别在于,属性装饰器是没有descriptor这个属性的,它只有target和propertyKey

function nameConfig(state: boolean = false): any {

return function(target: any, propertyKey: string) {

console.log(target, propertyKey);

};

}

class User implements UserInterface {

@nameConfig()

name = “Oliver”;

}

const user = new User();

user.name = “new oliver”;

console.log(user.name);

肯定有小伙伴问,能在装饰器中直接修改值吗,比如:

interface UserInterface {

name: string;

}

function nameConfig(state: boolean = false): any {

// target是原型

return function(target: any, propertyKey: string) {

target[propertyKey] = “new Oliver”;

};

}

class User implements UserInterface {

@nameConfig()

name = “Oliver”;

}

const user = new User();

console.log(user.name);

实际上是不行的,我们的target指的是原型,不信的话可以看,多打印一个

console.log(user.name); // Oliver

console.log((user as any).proto.name); // new Oliver

所以,这里要注意一下,并不能直接改值,另外,还会有小伙伴问,如果这个装饰器没有第三个参数,有没有办法和方法装饰器一样,禁止修改,比如:

class User implements UserInterface {

@nameConfig()

name = “Oliver”;

}

const user = new User();

user.name = “new oliver”; // 需要这里直接报错

答案肯定是有的

interface UserInterface {

name: string;

}

function nameConfig(state: boolean = false): any {

// target是原型

return function(target: any, propertyKey: string) {

// 改写name的属性描述符

const descriptor: PropertyDescriptor = {

writable: false

};

return descriptor;

};

}

class User implements UserInterface {

@nameConfig()

name = “Oliver”;

}

const user = new User();

user.name = “new oliver”; // 这里运行的时候会直接报错

console.log(user.name);

我们自己定义了一个描述符,然后将其返回,它的意思就是替换掉原来的属性描述符,这样外界在修改的时候会直接报错;

参数装饰器


再同样,用法和执行时机和类装饰器、方法装饰器一致在装饰器执行顺序中,参数装饰器是在方法装饰器执行完毕后执行,区别在于,参数装饰器是有第三个参数,但是不是descriptor是parameterIndex,target和propertyKey一摸一样,parameterIndex代表的是当前参数是第几个参数,类型是number

interface UserName {

name: string;

}

interface UserNameUserInterface {

name: string;

getName: (key: T) => string;

}

function nameConfig(state: boolean = false): any {

// target是原型

return function(target: any, propertyKey: string, parameterIndex: number) {

console.log(target, propertyKey, parameterIndex);

};

}

class User implements UserNameUserInterface {

constructor(public name: string) {}

getName(@nameConfig() key: T): string {

return this[key];

}

}

const user = new User(“Oliver”);

console.log(user.getName(“name”));

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ame: string) {}

getName(@nameConfig() key: T): string {

return this[key];

}

}

const user = new User(“Oliver”);

console.log(user.getName(“name”));

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-Ott7GEci-1715721203422)]

[外链图片转存中…(img-Q9gWvoxQ-1715721203423)]

[外链图片转存中…(img-jC3J8gDy-1715721203423)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值