参考

  • 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 readonlyPerson.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 facepalmingperson.facepalmHarder();// DEPRECATION Person#facepalmHarder: We stopped facepalming////     See http://knowyourmeme.com/memes/facepalm for more details.//

当咱们调用被deprecate装璜器装璜的办法facepalm时,控制台显示一条正告,示意该办法将破除,能够给deprecate传入不同的参数管制显示内容

通过下面的两个例子,咱们能够看出,装璜器优雅的扭转了类原来的行为,它是可复用的而且表白直观

装璜器是什么

装璜器实质是一种函数,写成@ + 函数名,它能够减少或批改类的性能

装璜器的用法

首先明确装璜器并不能在代码的任意地位应用,它只能用于类,要么放在类定义后面,要么放在类属性的后面

作用于类的装璜器(放在类定义的后面)

基本上,装璜器的行为就是上面这样

@decoratorclass A {}// 等同于class A {}A = decorator(A) || A;

也就是说,装璜器是一个对类进行解决的函数。装璜器函数的第一个参数,就是所要装璜的指标类。

示例三:应用装璜器给类增加动态属性

// 定义一个装璜器函数const addPropertyA = (target) => {  // 此处的target为类自身(即ClassA)  target.a = 'a'; // 给类(ClassA)增加一个动态属性};@addPropertyAclass ClassA {  constructor() {    this.a = 1;  }}console.info('ClassA.a: ', ClassA.a); // aconsole.info('a: ', new ClassA().a); // 1

tips:能够应用在线编译ts将下面的ts代码编译成的js(es2017)帮忙了解

示例四:应用装璜器给类增加实例属性

本例给类ClassA增加了原型办法

const addMethodTest = (target) => {  target.prototype.test = () => {    console.log('test');  };};@addMethodTestclass 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');@addPropertyAclass 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 readonlyPerson.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 readonlyPerson.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>'