本文将从以下几个角度解析装饰器:
1. 装饰器模式;
2. 装饰器的基本概念;
3. 装饰器的类型;
一、什么是装饰器模式:
要想正确理解设计模式,首先必须明确它是为了解决什么问题而提出来的。
那装饰器模式到底是为了解决什么问题呢?在我们的开发过程中我们会为了一些通用功能在多个不同的组件、接口或者类中使用,这个时候我们这些功能写到每个组件、接口或者类中,但是这样非常不利于维护。
装饰器就是为了解决在不修改原本组件、接口或者类的时候为其添加额外的功能。
从本质上看装饰器模式是一个包装模式((Wrapper Pattern),它是通过封装其他对象达到设计的目的的。
react的高阶组件本质也是装饰器模式,以后会有一篇文章会专门详解react的高阶组件。
理解了装饰器的能解决了什么问题,那我们一遍在什么情况下考虑使用装饰器模式呢?我的理解是:
- 需要扩展一个类,为这个类附加一个方法或者属性的时候;
- 需要修改一个类的功能,或者重构这个类中的某个方法;
二、装饰器的基本概念:
上面我们解释了什么是装饰器模式,下面我们将要ES7的装饰器的一些基本概念:如何定义装饰器、装饰器执行时机、装饰器类型。
1、如何定义装饰器
装饰器本质是一个函数,可以分为带参数和不带参数(也叫装饰器工厂),装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
@Test()
class Hello {}
function Test(target:any) {
console.log("I am decorator.")
}
2、装饰器执行时机
修饰器对类的行为的改变,是代码编译时发生的(不是TypeScript编译,而是js在执行机中编译阶段),而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数
3、装饰器的类型
类装饰器、属性装饰器、方法装饰器、参数装饰器
三、装饰器类型:
- 类修饰器
类装饰器一般主要应用于类构造函数,可以监视、修改、替换类的定义,装饰器用来装饰类的时候。装饰器函数的第一个参数,就是所要装饰的目标类本身。
a、添加静态属性或方法
@Test()
class Hello {}
function Test(target) {
target.a = 1;
}
let o = new Hello();
console.log(o.a) ==>1
b、添加实例属性或方法
@Test()
class Hello {}
function Test(target) {
target.prototype.a = 1;
target.prototype.f = function(){
console.log("新增加方法")
};
}
let o = new Hello();
o.f() ==>"新增加方法"
console.log(o.a) ==>1
c、装饰器工厂(函数柯里化)
很多文章说装饰器工厂是闭包,其实不准确,关于高阶函数、闭包、函数柯里化可以参考:
理解运用JS的闭包、高阶函数、柯里化
@Test('hello')
class Hello {}
function Test(str) {
return function(){
target.prototype.a = str;
target.prototype.f = function(){
console.log(str)
};
}
}
let o = new Hello();
o.f() ==>"hello"
console.log(o.a) ==>"hello"
d、重载构造函数
@Test('hello')
class Hello {
constructor(){
this.a= 1
}
f(){
console.log('我是原始方法',this.a)
}
}
function Test(target) {
return class extends target{
f(){
console.log('我是装饰器方法',this.a)
}
}
}
let o = new Hello();
o.f() ==>"我是装饰器方法",1
- 方法装饰器
它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列3个参数:
- target:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- key: 成员的名字。
- descriptor:成员的属性描述符。
descriptor:
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
方法装饰器最后必须返回descriptor
function nameDecorator(target, key, descriptor) {
descriptor.value = () => {
return 'jake';
}
return descriptor;
}
class Person {
constructor() {
this.name = 'jake'
}
@nameDecorator
getName() {
return this.name;
}
}
let p1 = new Person();
console.log(p1.getName())
另外一个经典例子
class Math {
@log
add(a, b) {
return a + b;
}
}
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function(...list) {
console.log(list);
console.log(`Calling ${name} with`, ...list);
return oldValue.call(this, ...list);
};
return descriptor;
}
const math = new Math();
// passed parameters should get logged now
let result = math.add(2, 4);
console.log(result)