【typescript】js装饰器与ts装饰器梳理

前言

  • 本篇梳理ts装饰器,装饰器很多文章里都有讲,不过很多文章少讲漏讲现象很多,先总结第一波,因为我暂时没理解透,结合nest还有很多地方有点问题,如果照nest写法,有些实现应该报错才对。这篇差点把我写吐了,一开始还想写,写后面越来越不想写。

聊一下JS装饰器

  • 为了有更好的学习效果,我们可以比较一下js装饰器,实际上,js装饰器和ts装饰器并不兼容,而且js装饰器曾有过大变动,这些在我百度的资料里都没有找到,都是我去github上翻到的。
  • js装饰器曾经有巨大改版,主要是在2个方面:
  • 第一个方面是多个装饰器在装饰类的原型方法时,执行顺序不同:
function dec(id){
    console.log('evaluated', id);
    return (target, property, descriptor) => console.log('executed', id);
}
class Example {
    @dec(1)
    @dec(2)
    method(){}
}
  • 老版本会这么输出:
evaluated 2
evaluated 1
executed 2
executed 1
  • 新版本会这么输出:
evaluated 1
evaluated 2
executed 2
executed 1
  • 第二个方面在于修饰类的静态属性时,老版本的descriptor没有initializer缓存值:
function dec(target, prop, descriptor){
    let {initializer} = descriptor;
    delete descriptor.initializer;
    delete descriptor.writable;
    descriptor.get = function(){
        return initializer.call(this);
    };
}
class Example {
    @dec
    static prop = i++;
}
  • 另外ts装饰器和js装饰器有个最大最明显的区别就是,ts装饰器的参数必须写全,否则报错,这是一个很奇怪的现象,相反,js装饰器就没这要求。而ts装饰器在修饰不同东西时装饰器的参数是不同的,参数数量也是不同的,所以这就导致ts装饰器上手比js装饰器难得多,并不是一个简单的语法糖。
  • 而js装饰器参数不用写全就可以使用,可以看一下babel对于js装饰器的写法:
  • babel地址
  • 由于js装饰器已经没落了,所以可以不用学了,直接学下ts装饰器,ts装饰器语法很稳定,而且已经逐渐被大量使用了。

装饰器类型

  • 装饰器有4种,类装饰器、属性装饰器、方法装饰器和参数装饰器。

类装饰器

  • 这个类装饰器,有很多说头,因为使用花样挺多的,为了方便学习,
  • 第一种方式,替换类的原型的方法与属性:
  • 接收参数为类的原型对象
//当装饰器作为修饰类的时候,会把构造器传递进去
function addNameEat(constructor: typeof Person) {
	constructor.prototype.name = "yehuozhili";
	constructor.prototype.eat = function () {
		console.log("eat");
	};
}
@addNameEat
class Person {
	name: string;
	eat: Function;
	constructor() {}
}
let p: Person = new Person();
console.log(p.name);
p.eat();
  • 注意 ! addNameEat并不是返回一个函数,所以装饰器后面没有执行的括号。
  • 第二种方式,返回函数进行执行,是第一种方法的传参版本:
function addNameEat(name:string) {
    return function (constructor: typeof Person) {
        constructor.prototype.name = name;
        constructor.prototype.eat = function () {
        console.log("eat");
        };
    };
    }
@addNameEat('yehuozhili')
class Person {
	name: string;
	eat: Function;
	constructor() {}
}
let p: Person = new Person();
console.log(p.name);
p.eat();
  • 第三种方式,返回个类替换原来的 ,替换的类有要求,必须要实现原有类的原型方法:
interface PersonExtends extends Person{
   bbb:number
   hello:Function
}
function addNameEat(name: string) {
	return function (_constructor: typeof Person) {
		return class  {
			name: string; //类型需要一致
			eat() {
				console.log("修改后");
			}
			hello() {
				//可以新增
				console.log("new function", name);
			}
			static aaa: string = "1";
			static bbb: number = 111;
		};
	};
}
@addNameEat("yehuozhili")
class Person {
	name: string;
	eat: Function;
	constructor() {}
	static aaa: string = "222";
}
let p = <PersonExtends>new Person();
console.log(p.name);//undefiend
p.eat();//修改后
p.hello();// new function yehuozhili
let k = <PersonExtends><unknown>Person
console.log(Person.aaa);//'1'
console.log(k.bbb)//111
  • 这么做感觉像反向继承的意思,新出来的实例获得了新的方法,新的属性。
  • 输入console.log(p instanceof Person)的时候,得到的结果也是true。
  • 直接增加这个方式少见,一般是使用Object.defineProperty或者Reflect.meta定义后直接返回:
function addNameEat(name:string) {
    return function (constructor: typeof Person) {
        //使用object.define 或者 meta进行扩展
       return constructor
    };
    }
@addNameEat('yehuozhili')
class Person {
	name: string;
	eat: Function;
	constructor() {}
}
let p: Person = new Person();
console.log(p.name);
p.eat();
  • nest的类装饰器也是这种方式,但是暂时没懂如何在类中constructor里传参private不赋值并且不会报错的。

属性装饰器 与 方法装饰器

  • 修饰实例属性:
function upperCase(target: any, propertyKey: string) {
	let value = target[propertyKey];
	const getter = function () {
		return value;
	};
	// 用来替换的setter
	const setter = function (newVal: string) {
		value = newVal.toUpperCase();
	};
	// 替换属性,先删除原先的属性,再重新定义属性
	if (delete target[propertyKey]) {
		Object.defineProperty(target, propertyKey, {
			get: getter,
			set: setter,
			enumerable: true,
			configurable: true,
		});
	}
}
  • 在修饰属性或者方法时,target为类的实例,propertyKey为修饰的那个属性,这里不接收propertyKey直接报错:作为表达式调用时,无法解析属性修饰器的签名,同理,多接收参数也不行。
  • 属性装饰器同时也可以装饰静态属性:
@test
	public static age: number = 10;
  • 此时target为类,第二个参数为age
  • 装饰类的原型方法:
function noEnumerable(
	target: any,
	_property: string,
	descriptor: PropertyDescriptor
) {
	console.log("target.getName", target.getName);
	console.log("target.getAge", target.getAge);
	descriptor.enumerable = true;
}
class Person {
    @upperCase
	name: string = "yehuozhili";
	public static age: number = 10;
	constructor() {}
	@noEnumerable
	getName() {
		console.log(this.name);
	}
	@toNumber
	sum(...args: any[]) {
		return args.reduce((accu: number, item: number) => accu + item, 0);
	}
}
  • 实例方法上的装饰器会比属性装饰器多个属性。target为实例,第二个为方法名,最后那个为描述器。
  • 装饰类的静态方法,同理,也是3属性,target则变为类。这个例子里第二个参数为aa,就是方法名。
@test
	static get aa() {
		return "ss";
	}

参数装饰器

  • 方法与属性装饰器不写第二个参数就报错,而参数装饰器更狠,第三个参数不接收直接报错。
  • 套路和上面一样:
interface Person {
    age: number;
}
function addAge(target: any, methodName: string, paramsIndex: number) {
    console.log(target);
    console.log(methodName);
    console.log(paramsIndex);
    target.age = 10;
}
class Person {
    login(username: string, @addAge password: string) {
        console.log(this.age, username, password);
    }
    static aaa(@addAge a:string){
        console.log(a)
    }
}
let p = new Person();
p.login('yehuozhili', '123456')
  • 静态方法的参数target则为类,原型方法的参数target则为类的原型。
  • 第二个参数就是方法名
  • 最后那个参数是该参数在此方法的索引,这个传索引过来有点引人深思。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值