js装饰器入门讲解

参考

提示

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

你可能感兴趣的:(js装饰器入门讲解)