装饰器主要有两个用处:
- 装饰类
- 装饰方法或属性
废话不多说,先上代码感受一下
下面是装饰类的:
function log(target, name, descriptor) {
console.log(target)//Car
console.log(name)//undefined
console.log(descriptor)//undefined
}
@log // 装饰类的装饰器
class Car {
run() {
console.log('Car is running')
}
}
const c1 = new Car()
c1.run()
下面是装饰方法的:
function log(target, name, descriptor) {
console.log(target)//class Car{}
console.log(name)//run
console.log(descriptor)//{writable: true, enumerable: false, configurable: true, value: ƒ}
}
class Car {
@log // 装饰方法的装饰器
run() {
console.log('Car is running')
}
}
const c1 = new Car()
c1.run()
今天主要探索一下装饰方法的源码~
首先看一段编译前的代码:
class MyClass {
@unenumerable
@readonly
method() { }
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
function unenumerable(target, name, descriptor) {
descriptor.enumerable = false;
return descriptor;
}
对类里面的方法使用不可枚举、只读的两个装饰器
使用babel编译后如下,耐心看完后,你会发现很简单:
var _class;
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) {
/**
* 第一部分
* 拷贝属性
*/
var desc = {};
Object["ke" + "ys"](descriptor).forEach(function(key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ("value" in desc || desc.initializer) {
desc.writable = true;
}
/**
* 第二部分
* 应用多个 decorators
*/
desc = decorators
.slice()
.reverse()
.reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
/**
* 第三部分
* 设置要 decorators 的属性
*/
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object["define" + "Property"](target, property, desc);
desc = null;
}
return desc;
}
let MyClass = ((_class = class MyClass {
method() {}
}),
_applyDecoratedDescriptor(
_class.prototype,
"method",
[readonly],
Object.getOwnPropertyDescriptor(_class.prototype, "method"),
_class.prototype
),
_class);
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
首先声明_applyDecoratedDescriptor函数,在下面调用并传参,参数里用到
Object.getOwnPropertyDescriptor()这个方法,我们首先介绍一下,举个栗子:
const foo = { value: 1 };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
// value: 1,
// writable: true
// enumerable: true,
// configurable: true,
// }
const foo = { get value() { return 1; } };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
// get: /*the getter function*/,
// set: undefined
// enumerable: true,
// configurable: true,
// }
就是简单返回Descriptor的方法
下面分为三部分介绍一下_applyDecoratedDescriptor函数,顾名思义,看这函数名大致就能知道这个函数要干啥的了把~~~
第一部分源码解析
在 _applyDecoratedDescriptor 函数内部,我们首先将 Object.getOwnPropertyDescriptor() 返回的属性描述符对象做了一份拷贝:
// 拷贝一份 descriptor
var desc = {};
Object["ke" + "ys"](descriptor).forEach(function(key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setter
if ("value" in desc || desc.initializer) {
desc.writable = true;
}
那么 initializer 属性是什么呢?Object.getOwnPropertyDescriptor() 返回的对象并不具有这个属性呀,确实,这是 Babel 的 Class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:
class MyClass {
@readonly
born = Date.now();
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
var foo = new MyClass();
console.log(foo.born);
Babel 就会编译为:
// ...
(_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return Date.now();
}
}))
// ...
此时传入 _applyDecoratedDescriptor 函数的 descriptor 就具有 initializer 属性。
第二部分源码解析
接下是应用多个 decorators:
/**
* 第二部分
* @type {[type]}
*/
desc = decorators
.slice()
.reverse()
.reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
对于一个方法应用了多个 decorator,比如:
class MyClass {
@unenumerable
@readonly
method() { }
}
Babel 会编译为:
_applyDecoratedDescriptor(
_class.prototype,
"method",
[unenumerable, readonly],
Object.getOwnPropertyDescriptor(_class.prototype, "method"),
_class.prototype
)
在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。
第三部分源码解析
/**
* 第三部分
* 设置要 decorators 的属性
*/
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object["define" + "Property"](target, property, desc);
desc = null;
}
return desc;
如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:
desc.initializer.call(context)
而 context 的值为 _class.prototype,之所以要 call(context),这也很好理解,因为有可能
class MyClass {
@readonly
value = this.getNum() + 1;
getNum() {
return 1;
}
}
最后无论是装饰方法还是属性,都会执行:
Object["define" + "Property"](target, property, desc);
由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。
完结撒花,有问题留言讨论哈~~~