TypeScript 高级语法
1. 类的装饰器
装饰器入门
装饰器本身是一个函数,装饰器通过 @ 进行调用。
要使用装饰器,tsconfig 需要添加允许装饰器的配置:
"experimentalDecorators": true, "emitDecoratorMetadata": true,
讯享网
尝试运行以下代码:
讯享网function testDecorator(constructor: any) {
console.log("decorator"); } @testDecorator class Test {
}
可以看到控制台输出了 “decorator”。类装饰器在类创建好后立即执行,和是否创建类实例无关。
类装饰器接收的函数是被修饰的类的构造函数。
当使用多个装饰器时候,离 class 越近的,就优先执行。
function testDecorator(constructor: any) {
console.log("decorator"); } function testDecorator1(constructor: any) {
console.log("decorator1"); } @testDecorator @testDecorator1 class Test {
} // 先输出 decorator1 再输出 decorator
工厂模式生成装饰器
执行一个函数来生成一个新的装饰器。
讯享网function testDecorator(text: string) {
return function(constructor: any) {
console.log(text); } } @testDecorator("hello") class Test {
}
使用装饰器扩展类的功能
直接放代码:
function testDecorator<T extends new (...args: any[]) => {
}>(constructor: T) {
return class extends constructor {
name = "decorator"; }; } @testDecorator class Test {
name: string; constructor(name: string) {
console.log(1); this.name = name; console.log(this.name); console.log(2); } } const test = new Test("sjh"); console.log(test);
输出结果:
讯享网1 sjh 2 Test { name: 'decorator' }
输出结果表明了,装饰器修改了类中的成员变量,同时,装饰器的执行是在类的构造器之后的。
装饰器上的泛型比较难理解,说白一点,就是指明 T 为具有构造函数的类型。下面详细解析内容:
new (...args: any[]) => {
}
该构造函数函数返回一个对象类型,这个函数能接受任意多的参数,并且不限制类型。
讯享网T extends new (...args: any[]
T 这种类型可以通过这种构造函数被实例化出来。
装饰器扩展类的方法
按照上面的写法,是这样写的:
function testDecorator<T extends new (...args: any[]) => {
}>(constructor: T) {
return class extends constructor {
name = "decorator"; getName() {
return this.name; } }; } @testDecorator class Test {
name: string; constructor(name: string) {
this.name = name; } } const test = new Test("sjh");
但是如果使用 @ 装饰器,TS 是无法提供拓展方法 getName 的提示的,因此得写成较为晦涩的写法:
讯享网function testDecorator<T extends new (...args: any[]) => {
}>(constructor: T) {
return class extends constructor {
name = "decorator"; getName() {
return this.name; } }; } const Test = testDecorator( class {
name: string; constructor(name: string) {
this.name = name; } } ); const test = new Test("sjh"); console.log(test.getName());
@ 装饰器直接写成更原生的方法,Test 就是经过修饰后的新的类。这样的话就能被识别,就可以获得提示了。
2. 方法装饰器
同样的,方法也有装饰器。
一般有的场景是,不允许原本的方法被修改,因此可以用方法装饰器进行改装来避免这个问题。还有的场景是,在原本方法的基础上添加新的特性。
方法装饰器本质也是函数,里边有三个参数:
function decorator(target: any, key: string, descriptor: PropertyDescriptor) {
// 函数执行内容 }
- target:如果是普通方法,target 对应的是类的 prototype;如果是静态方法,target 对应的是类的构造函数
- key:方法名
- descriptor:存着一些属性,用来控制该被装饰的函数
2.1 应用场景
- 类里的方法不可被修改
讯享网
function uneditable(target: any, key: string, descriptor: PropertyDescriptor) { // 该方法不可被重写 descriptor.writable = false; } class Test { name: string; constructor(name: string) { this.name = name; } @uneditable getName() { return this.name; } } const test = new Test("sjh"); test.getName = () => { return "123"; }; // 尝试修改不可被重写的方法,因此会报错 console.log(test.getName()); - 用方法装饰器修改方法,用
descriptor.value顶替掉原来的方法。function changeFunc(target: any, key: string, descriptor: PropertyDescriptor) { // descriptor.value = function getName descriptor.value = function () { return "modified"; }; } class Test { name: string; constructor(name: string) { this.name = name; } @changeFunc getName() { return this.name; } } const test = new Test("sjh"); console.log(test.getName()); // 输出 modified
3. 访问器的装饰器
复习一下访问器
讯享网class Test {
private _name: string; constructor(name: string) {
this._name = name; } get name() {
return this._name; } set name(name: string) {
this._name = name; } } const test = new Test("sjh"); test.name = "123"; console.log(test.name); // 输出 "123"
访问器修饰器的使用
访问器本质上还是方法,因此该咋用还是咋用。
例如,数据不可被修改:
function visitDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false; } class Test {
private _name: string; constructor(name: string) {
this._name = name; } get name() {
return this._name; } @visitDecorator set name(name: string) {
this._name = name; } } const test = new Test("sjh"); test.name = "123"; // setter 上的装饰器不允许修改内容,因此会报错 console.log(test.name);
4. 属性的装饰器
属性同样有装饰器,但是就不能接收到 descriptor 了。
第一个参数是 target,代表类的原型,第二个参数是 key,为属性名称。
自己建一个 descriptor
虽然没有 descriptor,但是可以自己写。例如,实现属性不可被改写。装饰器函数返回一个 descriptor,里边的 writable 改为 false,可以创造出不能被修改的属性。
讯享网function nameDecorator(target: any, key: string): any {
const descriptor: PropertyDescriptor = {
writable: false, }; return descriptor; } class Test {
@nameDecorator name = "sjh"; } const test = new Test(); test.name = "123"; // 报错 console.log(test.name);
target 指的是类的原型
function nameDecorator(target: any, key: string): any {
// Test.prototype.name = 123 target[key] = "123"; } class Test {
@nameDecorator name = "sjh"; } const test = new Test(); console.log(test.name);
上面的代码输出结果仍然是 “sjh”,原因在于,“sjh” 是在实例下的属性,而装饰器里的 “123” 被放置在了原型上。根据原型链的查找原则,优先找到实例下的属性。
如果要访问 “123”,则需要找实例的隐式原型上的 name 属性。
讯享网console.log((test as any).__proto__.name)
5. 参数装饰器
可以对类里的方法的参数进行装饰。
装饰器携带三个参数:
- target:类的原型
- method:方法名
- paramIndex:参数在方法里的 index
function paramDecorator(target: any, key: string, paramIndex: number): any {
console.log(target, key, paramIndex); } class Test {
getInfo(name: string, @paramDecorator age: number) {
console.log(name, age); } } const test = new Test(); console.log(test.getInfo("sjh", 18));
6. 装饰器的实际使用范例
获取对象里的属性,但是这个属性不一定存在,不存在的话给提示,普通写法会这样写:
讯享网class Test {
userInfo: any = undefined; getName() {
try {
return this.userInfo.name; } catch (e) {
console.log("userInfo.name 不存在") } } getAge() {
try {
return this.userInfo.age; } catch (e) {
console.log("userInfo.age 不存在") } } }
但是这样会有大量的重复代码,因此这里使用装饰器解决。
function catchError(msg: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
const fn = descriptor.value; descriptor.value = function () {
try {
fn(); } catch (e) {
console.log(msg); } }; } } class Test {
userInfo: any = undefined; @catchError("userInfo.name 不存在") getName() {
return this.userInfo.name; } @catchError("userInfo.age 不存在") getAge() {
return this.userInfo.age; } }
7. reflect-metadata
元数据是挂在对象上的数据,但是不能直接通过输出查看到。
讯享网yarn add reflect-metadata
添加和获取元数据内容基本使用
import "reflect-metadata"; const user = {
name: "sjh", }; Reflect.defineMetadata("data", "test", user); console.log(Reflect.getMetadata("data", user));
用装饰器添加和获取元数据
元数据可以添加到类、类方法、类属性上。
讯享网import "reflect-metadata"; @Reflect.metadata("data", "test") class User {
@Reflect.metadata("nameMeta", "hhh") name = "sjh"; } console.log(Reflect.getMetadata("data", User)); // 属性的元数据放在类的原型的对应属性上,方法同理 console.log(Reflect.getMetadata("nameMeta", User.prototype, "name"));
其他 API
hasMetadata // 有该元数据 hasOwnMetadata // 有该元数据且不是继承过来的 getMetadataKeys // 显示所有的元数据名称 deleteMetadata // 删除元数据
@Reflect.metadata 实现原理
本质上是一个函数,返回一个装饰器。
自己实现一个功能相同的注解:
讯享网function setData(dataKey: string, msg: string) {
return function (target: User, key: string) {
Reflect.defineMetadata(dataKey, msg, target, key); }; } @showData class User {
@Reflect.metadata("data", "name") getName() {
console.log("name"); } @setData("data", "age") getAge() {
console.log("age"); } }
8. 装饰器执行顺序
方法装饰器优先于类装饰器

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/128352.html