聊一聊Javascript的装饰器

1,093 阅读3分钟

突然想到装饰器, 也是因为公司的项目BFF层这边使用的nestjsnestjs中使用了大量的装饰器。 这样的写法简洁明了, 强约定性也便于后期的维护。

装饰器的好处

  1. 好的装饰器就是代码注释;
  2. 帮助代码解耦;
  3. 提高代码的复用性;
  4. 代码更加简洁, 更便于阅读;

装饰器的使用

装饰器目前可以理解为对类class的一种修饰和修改。装饰器本身是一个函数,可以放在类,类方法,类方法的参数定义前面。

function testD(target){
	return target.test = true
}

@testD
class Test {}

console.log(Test.test) 			// true

这里通过装饰器完成了对class内部属性的一个添加操作。

装饰类

上面的代码就是对类的一种装饰,它接受一个target参数就是类Test本身。

如果想要传参可以这样:

function testD(params){
	return function(target){
    	return target.test = params
    }
}

@testD(true)
class Test{}

上面的代码都是在类身上添加静态属性, 如果想要添加实例属性可以考虑添加到Test.prototype上。

装饰类的方法

class Person {
    @nonenumerable
    get kidCount() { return this.children.length; }
}

function nonenumerable(target, name, descriptor) {
    descriptor.enumerable = false;
    return descriptor;
}

装饰类的方法支持三个参数, 第一个参数是类的原型对象,第二个参数是所要装饰的属性名, 第三个参数是属性的描述对象, 上面的装饰器实现了属性不可遍历。

装饰类的方法的参数

函数参数装饰器会接收三个参数:

  1. 类的原型
  2. 参数所处的函数名称
  3. 参数在函数中形参中的位置(函数签名中的第几个参数)

装饰函数参数主要应用场景还是做参数的校验和转换。

Reflect.metadata

在定义类或者类方法的时候,可以设置一些元数据,我们可以获取到在类与类方法上添加的元数据,用的方法就是 Reflect Metadata。元数据指的是描述东西时用的数据。

@Reflect.metadata('name', 'zhangsan')
class People{}

const data = Reflect.getMetadata('name', People)
console.log(data) 			//zhangsan

上面是直接使用Reflect.metadata作为装饰器, 要想实现比较复杂的功能, 也可以考虑自定义:

function Name(params){
	return (target) => {
		Reflect.defineMetadata('name', params, target)
    }
}

@Name('zhangsan')
class People{}

const data = Reflect.getMetadata('name', People)
console.log(data) 			//zhangsan

项目中使用装饰器

目前装饰器还在提案阶段,babel是支持编译装饰器的, 要想在项目中使用, 可以安装插件babel-plugin-transform-decorators-legacy@babel/plugin-proposal-decorators;

如果ide是vscode, 在setting.json中配置"javascript.implicitProjectConfig.experimentalDecorators": true

要使用Reflect.metadata需要安装reflect-metadata包,如果是ts项目,并且配置tsconf.json

{
  "compilerOptions": {
    "experimentalDecorators": true ,
    "emitDecoratorMetadata": true
  },
}

后话

装饰器是在编译时执行的,而不是运行时。 可以理解为装饰器就是一个编译时执行的对类进行修饰的函数

装饰器为什么不能用于函数: 因为存在函数提升, 而类没有。

参考:

es6.ruanyifeng.com/#docs/decor…

juejin.cn/post/684490…