关于javascript:js装饰器入门讲解

参考

  • 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>'

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理