参考
- js 装璜器 @Decorator
- 装璜器 - 阮一峰
提醒
- 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 '#<Person>'