简介
在一些框架(比如 nest.js
、midway.js
等)中,经常看到在类代码的附近有 @......
这种代码,这就是 Decorator
Decorator
可以叫做修饰器,或者是装饰器
修饰器是一种特殊类型的声明,它只能够被附加到类的声明、方法、属性或参数上,可以修改类的行为。但不能用于函数(因为存在函数提升)
常见的修饰器有:类修饰器
、属性修饰器
、方法修饰器
、参数修饰器
修饰器写法:普通修饰器(无法传参)、修饰器工厂(可传参)
修饰器对类行为的改变,是代码编译时发生的,而不是在运行时。修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数
修饰器是一个对类进行处理的函数。修饰器函数的第一个参数,就是所要修饰的目标类
修饰器的行为类似如下
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A
类修饰器
类修饰器在类声明之前被声明(紧靠着类声明)
类修饰器应用于类构造函数,可以用来监视,修改或替换类定义
- 类修饰器示例
function logClass (target) {
console.log(target) // [Function: MyTest] target指向修饰的类
target.nTest = '扩展的静态属性' // 扩展静态属性
target.prototype.nName = '动态扩展的属性' // 给原型扩展属性
target.prototype.nFun = () => {
console.log('动态扩展的方法')
}
}
@logClass
class MyTest {}
const test = new MyTest()
console.log(test.nTest) // 扩展的静态属性
console.log(test.nName) // 动态扩展的属性
test.nFun() // 动态扩展的方法
- 修饰器工厂(闭包传参)
function logClass (params) {
return function (target) {
console.log(target) // [Function: MyTest]
console.log(params) // hello
target.prototype.nName = '动态扩展的属性'
target.prototype.nFun = () => {
console.log('动态扩展的方法')
}
}
}
@logClass('hello')
class MyTest {}
const test = new MyTest()
console.log(test.nName) // 动态扩展的属性
test.nFun() // 动态扩展的方法
- Mixins 混入例子
// mixins.js 可以返回一个函数
export function mixins(...list) {
return function(target) {
Object.assign( target.prototype, ...list )
}
}
// main.js
import { mixins } from './mixins.js'
const Foo = {
foo() {console.log('foo')}
}
@mixins(Foo) // 当函数调用,传入参数
class MyClass {}
const obj = new MyClass()
obj.foo // 'foo'
- 重载构造函数的例子
function logClass (target) {
return class extends target {
attr = '重载属性'
getData () {
console.log('重载方法', this.attr)
}
}
}
@logClass
class MyTest {
attr
constructor () {
this.attr = '构造函数的属性'
}
getData () {
console.log(this.attr)
}
}
const test = new MyTest()
test.getData() // 重载方法 重载属性
-
React
+Redux
例子
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
// 可改写成
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
属性修饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列 2
个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 修饰的属性名
function logProperty (params) {
return function (target, name) {
console.log(target, name) // MyTest { getData: [Function] } 'attr'
target[name] = params
}
}
class MyTest {
@logProperty('属性修饰器的参数')
attr
constructor () {}
getData () {
console.log(this.attr) // 属性修饰器的参数
}
}
const test = new MyTest()
test.getData()
方法修饰器
它会被应用到方法的属性描述符上,可以用来监听、修改或者替换方法定义
修饰器会修改属性的描述对象,然后被修改的描述对象再用来定义属性
方法修饰器会在运行时传入下列 3
个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 修饰的属性名
- 该属性的描述对象
- 方法修饰器示例
// 方法修饰器
function logFunction (params) {
return function (target, methodName, desc) {
console.log(target, methodName, desc)
/*
MyTest { getData: [Function] }
'getData'
{
value: [Function], // 值
writable: true, // 可读
enumerable: true, // 可枚举
configurable: true // 可设置
}
*/
target.nName = '动态扩展的属性'
target.nFun = () => {
console.log('动态扩展的方法')
}
// 将接收到的参数改为 string 类型
const oMethod = desc.value
desc.value = function (...args) {
args = args.map((v) => {
return String(v)
})
return oMethod.apply(this, args)
}
}
}
class MyTest {
@logFunction('方法修饰器的参数')
getData (...args) {
console.log(args)
}
}
const test = new MyTest()
console.log(test.nName) // 动态扩展的属性
test.nFun() // 动态扩展的方法
test.getData(123, '234', () => {}) // [ '123', '234', 'function () { }' ]
- 输出日志的例子
class Math {
@log
add(a, b) {
return a + b;
}
}
// @log修饰器的作用就是在执行原始的操作之前,执行一次console.log,从而达到输出日志的目的
function log(target, name, descriptor) {
const oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
- 修饰器有注释的作用,看上去一目了然
@Component({
tag: 'my-component',
styleUrl: 'my-component.scss'
})
export class MyComponent {
@Prop() first: string; // props
@Prop() last: string; // props
@State() isVisible: boolean = true; // props
render() {
return (
Hello, my name is {this.first} {this.last}
);
}
}
参数修饰器
参数修饰器表达式会在运行时当作函数被调用,可以使用参数修饰器为类的原型增加一些元素数据,传入下列 3
个参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 参数的名字
- 参数在函数参数列表中的索引
function logParams (params) {
return function (target, methodName, paramsIndex) {
// MyTest { getData: [Function] } 'getData' 0
console.log(target, methodName, paramsIndex)
target.param = params
}
}
class MyTest {
getData (@logParams('参数修饰符的参数') id) {
console.log(id)
}
}
const test = new MyTest()
test.getData(123)
console.log(test.param) // 参数修饰符的参数
执行顺序
属性修饰器 > 参数修饰器 > 方法修饰器 > 类修饰器
function log (params) {
return function () {
console.log(params)
}
}
@log('类修饰器')
class MyTest {
@log('属性修饰器')
id
@log('方法修饰器')
getData (@log('参数修饰器') id) {
this.id = id
}
}
new MyTest()
- 同一个方法有多个修饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行
function dec(id){
console.log('evaluated', id);
return (target, property, descriptor) => console.log('executed', id);
}
class Example {
@dec(1)
@dec(2)
method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
不能用于函数
修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
// 意图是执行后counter等于 1,但是实际上结果是counter等于 0
var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
上面的代码,函数提升后相当于
@add
function foo() {
}
var counter;
var add;
counter = 0;
add = function () {
counter++;
};