参考
提示
- TypeScript 已经完整的实现了装饰器,js装饰器则是一个尚在提案中的语法,如果使用js而非ts,则需要配置Babel才能使用
- 阅读此文前需要了解的前置知识:js类,Object.defineProperty,js原型,闭包
常用的装饰器举例
我们抛开装饰器是怎么实现的,先来看两个装饰器的例子,以便对装饰器有初步的了解
示例一:readonly
class Person {
constructor(name) {
this.name = name
}
getName() { return this.name }
}
上面的代码定义了一个类Person,它有一个原型方法getName,显然getName是可以被修改的
Person.prototype.getName = fuction() { return `哈哈哈` }
console.log(new Person('test').getName()); // 哈哈哈
假如我现在要求getName不能被修改,使用装饰器可以达到效果
// 这是readonly的具体实现,可以先忽略,后面会详细介绍
function readonly(target, name, descriptor) {
descriptor.writable = false;
}
class Person {
constructor(name) {
this.name = name;
}
@readonly
getName() {
return this.name;
}
}
// 下面语句会报错,提示getName readonly
Person.prototype.getName = function () {
return `哈哈哈`;
};
console.log(new Person('test').getName());
上面的代码,我们给getName方法的定义上面加了一行@readonly
,这就是装饰器的写法,非常直观,就像注释一样,一眼就看出getName这个方法是只读的,不能修改
示例二:deprecate
当我们调用第三方库的方法的时候,常常会在控制台看到一些警告提示这个方法即将被移除,使用装饰器可以达到这个效果
import { deprecate } from 'core-decorators'; // 是一个第三方模块,提供了几个常见的装饰器
class Person {
@deprecate
facepalm() {}
@deprecate('We stopped facepalming')
facepalmHard() {}
@deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
facepalmHarder() {}
}
const person = new Person();
person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.
person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming
person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.
//
当我们调用被deprecate装饰器装饰的方法facepalm时,控制台显示一条警告,表示该方法将废除,可以给deprecate传入不同的参数控制显示内容
通过上面的两个例子,我们可以看出,装饰器优雅的改变了类原来的行为,它是可复用的而且表达直观
装饰器是什么
装饰器本质是一种函数,写成@ + 函数名,它可以增加或修改类的功能
装饰器的用法
首先明确装饰器并不能在代码的任意位置使用,它只能用于类,要么放在类定义前面,要么放在类属性的前面
作用于类的装饰器(放在类定义的前面)
基本上,装饰器的行为就是下面这样
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
示例三:使用装饰器给类添加静态属性
// 定义一个装饰器函数
const addPropertyA = (target) => {
// 此处的target为类本身(即ClassA)
target.a = 'a'; // 给类(ClassA)添加一个静态属性
};
@addPropertyA
class ClassA {
constructor() {
this.a = 1;
}
}
console.info('ClassA.a: ', ClassA.a); // a
console.info('a: ', new ClassA().a); // 1
tips:可以使用在线编译ts将上面的ts代码编译成的js(es2017)帮助理解
示例四:使用装饰器给类添加实例属性
本例给类ClassA添加了原型方法
const addMethodTest = (target) => {
target.prototype.test = () => {
console.log('test');
};
};
@addMethodTest
class ClassA {}
new ClassA().test(); // test
示例五:传参的类装饰器
示例三中,我们给ClassA这个类添加了一个静态属性a, 它的值是字符串'a'(一个写死的字面量),如果属性a的值需要由参数传入呢?此时需要在装饰器外再封装一层函数
function addPropertyA(value) {
return function (target) {
target.a = value;
};
}
// 箭头函数写法
// const addPropertyA = (value) => (target) => {
// target.a = value;
// };
@addPropertyA('test')
class ClassA {}
console.info('ClassA.a: ', ClassA.a); // test
上面的写法等同于
function getAddPropertyA(value) {
return function (target) {
target.a = value;
};
}
const addPropertyA = getAddPropertyA('test');
@addPropertyA
class ClassA {}
console.info('ClassA.a: ', ClassA.a); // test
作用于类属性的装饰器(放在类属性定义的前面)
示例六:readonly
再来看文章开头的示例一,我们有一个Person类
class Person {
constructor(name) {
this.name = name
}
getName() { return this.name }
}
假如我现在要求getName不能被修改,可以改用Object.defineProperty来定义getName
class Person {
constructor(name: string) {
this.name = name;
}
}
Object.defineProperty(Person.prototype, 'getName', {
writable: false,
value() {
return this.name;
},
});
// 下面语句会报错,提示getName readonly
Person.prototype.getName = function () {
return `哈哈哈`;
};
console.log(new Person('test').getName());
function readonly(target, name, descriptor) {
descriptor.writable = false;
}
class Person {
constructor(name) {
this.name = name;
}
@readonly
getName() {
return this.name;
}
}
// 下面语句会报错,提示getName readonly
Person.prototype.getName = function () {
return `哈哈哈`;
};
console.log(new Person('test').getName());
以上两种写法达到了同样的效果,但是第二种明显更优雅
readonly是装饰器函数,第一个参数是类的原型对象,上例是Person.prototype(注意不同于作用于类的装饰器),第二个参数是所要装饰的属性名(上例是getName),第三个参数是该属性的描述对象(同Object.defineProperty中传入的描述对象)。
上面代码说明,装饰器(readonly)可以修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性
示例七:实例属性装饰器
装饰器不仅可以作用于类方法,也可以作用于普通的类属性
import { deprecate } from 'core-decorators'; // 是一个第三方模块,提供了几个常见的装饰器
class Person {
@readonly name = 'Jack';
}
const person = new Person();
person.name = 'Rose'; // TypeError: Cannot assign to read only property 'name' of object '#'