【TypeScript】从零开始玩转TypeScript - TypeScript中的装饰器

前言

小伙伴们大家好。今天我们继续学习TypeScript。在开始今天的主题之前我们先来看一个场景:小A入职了一家公司并接手了一个后台项目,目前这个后台项目有很多问题,比如系统中的一些增删改查操作不需登录就可操作,这显然很不合理。于是项目经理就要求小A给所有的增删改查添加登录验证操作,需要用户登录后才能操作,一开始小A把每个方法中都添加了相同的登录校验代码,后来写着写着发现所添加的代码都是相同的,于是干脆封装成了一个check方法,然后再在每个方法中去调用。开发完成感觉良好,但没过几天项目经理突发奇想跟小A说:本着封闭开放的原则,这些增删改查的方法是别人已经封装好的了,那么我们对这些方法的内部代码就不要去修改了,但在不改变其内部代码的情况下登录校验功能同样也得加。这下可难坏小A了。不让我改内部代码还有得增加校验功能。于是网上各种搜寻查找,一次偶然的机会让他发现:诶,有个叫装饰器的家伙刚好能够满足需求。于是就引出了我们今天的主题 - 装饰器

装饰器

我们都知道在JavaScript中目前是没有装饰器的,而在TypeScript中已经支持了装饰器,但也是仅仅作为一项实验性的特性来支持,也就是说还不算是正式支持装饰器。那么如果我们想要在TypeScript中使用装饰器则,需要借助tsconfig.json或命令行中启用这一实验性特性。

  • tsconfig.json
{
    
    
	"compilerOptions":{
    
    
		"target":"ES5",
		"experimentalDecorators": true
	}
}
  • 命令行
tsc --target ES5 --experimentalDecorators

以上二选一即可。
接下来来看下官方给出的关于装饰器的定义:装饰器是一种特殊类型的声明,它能够被附加到类,方法,访问符,属性或者参数上,装饰器使用**@expression**的形式定义,其中expression求值后必须是一个函数,它会在运行时被调用,而被装饰的声明信息会作为参数传给它。其实说白了:装饰器的本质就是一个函数,它可以让其它函数在不需要做任何代码改动的前提下增加额外的功能。装饰器有如下几类:

  • 类装饰器:类装饰器在类声明之前被声明,类装饰器有如下几个特点:
  • 定义类的装饰器函数时,必须且只能接收一个参数,类的构造函数将作为实参传给装饰器
  • 类装饰器必须紧跟着类的声明,不能用在声明文件中(.d.ts),也不能用在任何外部上下文中
  • 类装饰器虽然是紧跟着类的声明,但实际上类装饰器是作用于类的构造函数的
  • 类装饰器在运行时被当作函数执行,并且会把类的构造函数作为装饰器的唯一参数
  • 类装饰器可用于监视,修改或者替换类的定义
//类装饰器,在不用new的情况下就可以调用类的构造函数
//必须接收一个参数
function hello(cons){
    
    
	//cons就是被装饰类的构造函数
	cons();	
}

@hello
class HelloWorld{
    
    
	constructor(){
    
    
		console.log('我在类装饰器中被调用了');
	}
}
// tsc编译后运行结果输出:=================
// "我在类装饰器中被调用了"
  • 方法装饰器:方法装饰器声明在方法声明之前,方法装饰器的特点:
  • 方法装饰器必须紧跟着方法的声明,且声明在方法之前
  • 方法装饰器不能声明在声明文件(.d.ts)中、也不能用在任何外部上下文中(一般多用于方法中)
  • 方法装饰器也不能用在重载函数中
  • 方法装饰器在运行时也是会被当做函数运行,但与类装饰器不同的是:方法装饰器在运行时会接收3个参数:
    • a).target:对于静态成员来说是类的构造函数,对于实例成员则是类的原型对象 xxx.prototype
    • b).propertyKey(string):成员的名字(一般是方法名)
    • c).descriptor(PropertyDescriptor):成员的属性描述(如果代码输出目标版本低于ES5,则属性描述是undefined)
  • 方法装饰器的定义一般都会定义为装饰器工厂的形式(可理解为函数柯理化的形式)
  • 方法装饰器可在不改变方法内部代码的情况下为方法增加额外的功能
//函数装饰器定义为装饰器工厂(柯理化函数)
function hello(){
    
    
	//接收3个参数
	return function(target:any, propertyKey:string, descriptor:PropertyDescriptor){
    
    
		console.log(`我是方法装饰器:${
      
      propertyKey},我比它先运行`)
	}
}

class HelloWorld{
    
    
	
	@hello()
	sayHi(){
    
    
		console.log('我被装饰器hello装饰了,它比我先运行');
	}
}
// tsc编译后运行结果输出两句话:=================
// "我是方法装饰器:hello,我比它先运行"
// "我被装饰器hello装饰了,它比我先运行"
  • 访问器装饰器:访问器装饰器声明在访问器声明之前,访问器装饰器的特点:
  • 访问器装饰器必须紧跟着访问器声明,且声明在访问器前面(顶部)
  • 访问器装饰器不能声明在声明文件(.d.ts)或者任何外部上下文中
  • 访问器装饰器不能同时装饰一个成员的get和set访问器
  • 访问器装饰器在运行时会被作为函数调用
  • 访问器装饰器与方法装饰器一样也需要接收3个参数:
    • 1.target:对于静态成员来说是类的构造函数,对于实例成员是累的原型对象
    • 2.propertyKey(string):成员的名字(访问器名)
    • 3.descriptor(PropertyDescriptor):成员的属性描述
  • 访问器装饰器可用于访问器的属性描述
//利用访问器装饰器将类的属性的configurable改成false
function config(val:boolean){
    
    
	//接收3个参数
	return function(target:any, propertyKey:string, descriptor:PropertyDescriptor){
    
    
		console.log(`我是访问器装饰器:${
      
      propertyKey},我来修改configurable啦`)
		console.log(descriptor.configurable)
		descriptor.configurable = val;
		console.log(descriptor.configurable)
	}
}

class HelloWorld{
    
    	
	yannis:string;
	constructor(yannis:string){
    
    
		this.yannis = yannis;
	}
	
	@config(false)
	get Yannis(){
    
    
		return this.yannis;
	}
}
// tsc编译后运行结果:=================
// "我是访问器装饰器:config,我来修改configurable啦"
// true
// false
  • 属性装饰器:属性装饰器声明在属性声明之前,属性装饰器有如下特点:
  • 属性装饰器的声明必须紧跟着属性的声明
  • 属性装饰器不能声明在声明文件(.d.ts)或者任何外部上下文中
  • 属性装饰器在运行时会被作为函数调用
  • 属性装饰器接收2个参数:
    • 1.target:对于静态成员是类的构造函数,对于实例成员是类的原型对象
    • 2.propertyKey(string):成员的名字(属性名)
  • 属性装饰器没有第三个参数,也就是说属性描述不会作为参数传给属性装饰器
//属性装饰器
function hello(){
    
    
	console.log('hello');
}

class HelloWorld{
    
    
	@hello()
	yannis:string;
}
// tsc编译后运行结果:=================
// "hello"
  • 参数装饰器: 参数装饰器声明在参数之前,参数装饰器特点如下:
  • 参数装饰器必须紧挨着参数
  • 参数装饰器不能声明在声明文件(.d.ts)或任何外部上下文中
  • 参数装饰器在运行时会被作为函数调用
  • 参数装饰器接收3个参数:
    • target:对于静态成员是类的构造函数,对于实例成员是类的原型对象
    • propertyKey(string):成员名称(参数名)
    • index(number):参数在函数参数列表中的索引
  • 参数装饰器用来并且只能用来监视一个方法的参数是否被传入
//参数装饰器
function hello(target:any,propertyKey:string, paramsIndex:number){
    
    
	console.log('hello');
}

class HelloWorld{
    
    

	sayHi(@hello name:string){
    
    
	
	}
}
// tsc编译后运行结果:=================
// "hello"

以上就是TypeScript中的装饰器了。
另外装饰器可以组合使用,就是说可以用多个装饰器同时装饰一个声明。比如一个saiHi方法同时可以用hello装饰器和sayHello装饰器进行装饰。组合装饰器装饰时有两个特点:

  • 装饰时从上向下装饰
  • 执行时从下向上执行
//参数装饰器
function hello(){
    
    
	console.log('hello从上向下装饰');
	return function(){
    
    
		console.log('hello从下向上执行')
	}
}

function sayHello(){
    
    
	console.log('sayHello从上向下装饰');
	return function(){
    
    
		console.log('sayHello从下向上执行')
	}
}

class HelloWorld{
    
    

	@hello()
	@sayHello()
	sayHi(){
    
    
		console.log('最后执行方法')
	}
}
let h = new HelloWorld();
h.sayHi();
// tsc编译后运行结果:=================
'hello从上向下装饰'
'sayHello从上向下装饰'
'sayHello从下向上执行'
'hello从下向上执行'
'最后执行方法'

总结

本文我们分享了TypeScript中的装饰器的知识。目前TypeScript中的装饰器仍然处于实验性阶段,个人看法:目前对于我们日常开发来说帮助不大,并且官方给出的关于装饰器的信息也不是很多,但既然出了那么我们也就顺便了解一下,技多不压身嘛。

好了小伙伴们,本次分享就到这里了,喜欢的小伙伴欢迎点赞留言加关注哦!

Guess you like

Origin blog.csdn.net/lixiaosenlin/article/details/121289788