装饰器在其他语言比如Python、Java中早就存在了。而在JavaScript中,直到目前仍处于stage2阶段的提案,这表示虽然未来应该会成为语言的一部分,但现在浏览器或Node都还不支持该特性,必须依赖于转译器。
修饰器(Decorator)是一个函数,用来修改类的行为。这是ES7的一个提案,目前Babel转码器已经支持。
在不改变原对象的基础上,为对象动态地增加职责的方式称为装饰者模式。
出现装饰器的原因:
装饰器优点:
我们如果要设计一个穿搭的应用程序。主题以人这个类为基础。
春夏秋冬我们的衣服穿搭是不一样的。
夏天:在人物类型上,搭配短裤短袖。
秋天:在人物类型上,搭配长裤、长袖、外套
冬天:在人物类型上,搭配羽绒服、冲锋衣、毛衣等等
搭配的服饰肯定不属于人物类型身体的一部分。我可以根据天气随意给人物类型搭配服饰。并且同一件服饰还可以给不同的人物搭配。或者同一个人物类型还可以搭配多种服饰。可以随意搭配、随意脱掉。
人物类型确定过后,服饰搭配就是我们需要给人物进行装饰。这个过程映射到系统程序,就是装饰器模式。不断的在原有的代码基础上扩展也的业务和功能。不影响原来程序本身的属性和行为。
JavaScript装饰器呢,就是对类、类属性、类方法之类的一种装饰,可以理解为在原有代码外层又包装了一层处理逻辑。这样就可以做到不直接修改代码,就实现某些功能。
在JavaScript代码中高阶函数就可以实现装饰器的效果。通过调用一个函数来包装另外一个函数。
代码如下:
function people(name) {
console.log('Hello, iam ' + name);
}
function peopleDecorator(wrapped) {
return function () {
console.log('穿搭衣服');
const result = wrapped.apply(this, arguments);
console.log('穿搭结束');
return result;
}
}
const result = peopleDecorator(people);
result()
效果如下:
调用:people()
//Hello, iam undefined
调用:result()
//穿搭衣服
//Hello, iam undefined
//穿搭结束
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
在TypeScript代码中配置TS编译选项
{
"compilerOptions":{
"experimentaDecorators":true
}
}
代码如下:
const decoratorHat = (targetClass:any) => {
//类属性
targetClass.hat = "鸭舌帽"
//成员属性
targetClass.prototype.hotColor = "red"
}
@decoratorHat
class People {
constructor(){}
}
const people = new People()
console.log(People.hat);
console.log(people.hotColor);
不管是new一个实例出来,还是直接获取类的属性,都可以打印出对应的值。
修饰器函数的第一个参数,就是所要修饰的目标类。
还可以让装饰器接受参数,这就等于可以修改装饰器的行为了,这也叫做装饰器工厂。装饰器工厂是通过在装饰器外面再封装一层函数来实现。
const decoratorHat = (value:any) => {
return (targetClass:any)=>{
console.log("-----",targetClass);
//类属性
targetClass.hat = "鸭舌帽"
//成员属性
targetClass.prototype.hotColor = value
}
}
@decoratorHat("red")
class People {
constructor(){}
}
const people = new People()
// @ts-ignore
// console.log(People.hat);
console.log(people.hotColor);
装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数
类属性装饰器可以用在类的单个成员上,无论是类的属性、方法、get/set函数。该装饰器函数有3个参数:
比如在一个类中,制定的某个属性属于只读属性,无法修改值。我们可以设计一个@readonly
//不传递参数
function readonly(target: any, name: any, descriptor: any) {
descriptor.writable = false
return descriptor;
}
//传递参数
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class People {
@readonly
get username() {
return "xiaofeifei"
}
@enumerable(false)
get password(){
return "123"
}
}
const people = new People()
console.log("输出people.username", people.username);
people.username = "xiaowang"
people.password = "666"
定义类的属性的时候,使用get方法来定义,这样我们可以在readonly函数中接受三个参数
可以给函数的方法添加装饰器。
实际案列:记录函数的调用日志记录
//不传递参数
function readonly(target: any, name: any, descriptor: PropertyDescriptor) {
descriptor.enumerable = false
return descriptor;
}
//传递参数
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
function log(target:any, name:any, descriptor:any) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function (...args:any[]) {
console.log(`${name} Arguments: ${args}`);
try {
const result = original.apply(this, args);
console.log(`${name} Result: ${result}`);
return result;
} catch (e) {
console.log(`${name} Error: ${e}`);
throw e;
}
}
}
return descriptor;
}
class People {
@readonly
get username() {
return "xiaofeifei"
}
@enumerable(false)
get password() {
return "123"
}
@log
play(params1:number,params2:number){
return params1 + params2
}
}
const people = new People()
console.log("输出people.username", people.username);
// people.username = "xiaowang"
// people.password = "666"
console.log(people.play(100,200));
同样使用@符号给属性添加装饰器,他会返回三个参数
代码如下
const currency: ParameterDecorator = (targer: any, key: string | symbol, index: number) => {
console.log(targer, key, index)
}
class People {
get username() {
return "xiaofeifei"
}
get password() {
return "123"
}
play(@currency params1:number,params2:number){
return params1 + params2
}
}
const people = new People()
people.play(100,200)