基本原理解析
修饰类
一个例子
@log1
@log2
@log3
class Person {
kidCount () { return this.children.length; }
}
function log1(target) {
console.log(target)
console.log(1)
return descriptor;
}
function log2(target) {
console.log(target)
console.log(2)
return descriptor;
}
function log3(target) {
console.log(target)
console.log(3)
return descriptor;
}
let p = new Person()
console.log(Person)
先用babel转一下,看编译后的结果
// proposal是提案的意思
npm i @babel/cli @babel/core @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
// .babelrc
{
"presets": [
"@babel/preset-env"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true}],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
这是结果
"use strict";
var _class;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Person =
log1(
(_class =
log2(
(_class =
log3(
(_class =
/*#__PURE__*/
(function() {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [
{
key: "kidCount",
value: function kidCount() {
return this.children.length;
}
}
]);
return Person;
})())
) || _class)
) || _class)
) || _class;
function log1(target) {
console.log(target);
console.log(1)
return descriptor;
}
function log2(target) {
console.log(target);
console.log(2)
return descriptor;
}
function log3(target) {
console.log(target);
console.log(3)
return descriptor;
}
var p = new Person();
console.log(Person);
其实就是将类不断传入装饰器。
修饰器不仅可以修饰类,还可以修饰类的属性。
修饰类的属性
举两个例子,分别装饰实例属性和原型属性
第一个,装饰实例属性
function readonly (target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Person {
name () { return `${this.first} ${this.last}` }
@readonly
xxx = 1
yyy = 2
}
let p = new Person()
console.log(p)
这是结果
"use strict";
var _class, _descriptor, _temp;
// initializer是插件自身提供的,描述符上并没有这个属性。
// 只有修饰实例属性的时候用得到,可以在下面看到
function _initializerDefineProperty(target, property, descriptor, context) {
if (!descriptor) return;
Object.defineProperty(target, property, {
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
writable: descriptor.writable,
value: descriptor.initializer
? descriptor.initializer.call(context)
: void 0
});
}
// 检验是否用new运算符初始化,这是this就是instance,必须为构造函数的实例
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
// 处理对象描述符,主要有以下几个注意点
// 1. 默认不可遍历
// 2. 默认可被删除
// 3. value和get不能共存,有value就对应writeable默认为true,代表可写。注意:writeable为false时,进行写操作也不会报错,只是不会生效
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
// 给类的原型属性和静态属性赋值
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
// target 是类的原型
function _applyDecoratedDescriptor(
target,
property,
decorators,
descriptor,
context
) {
var desc = {};
Object.keys(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;
}
// 核心逻辑就这几句,主要是从存储数组中从后往前执行修饰器函数
// 最终得到一个描述符,用来描述这个属性的一些行为。
desc = decorators
.slice()
.reverse()
.reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
// 如果是undefined,说明是原型对象(插件逻辑规定的),直接在原型对象上定义该属性。
if (desc.initializer === void 0) {
Object.defineProperty(target, property, desc);
desc = null;
}
return desc;
}
function _initializerWarningHelper(descriptor, context) {
throw new Error(
"Decorating class property failed. Please ensure that " +
"proposal-class-properties is enabled and set to use loose mode. " +
"To use proposal-class-properties in spec mode with decorators, wait for " +
"the next major version of decorators in stage 2."
);
}
// 修饰器函数
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
// 用了一些括号运算符
var Person = ((_class = ((_temp =
/*#__PURE__*/
(function() {
function Person() {
_classCallCheck(this, Person);
_initializerDefineProperty(this, "xxx", _descriptor, this);
this.yyy = 2;
}
_createClass(Person, [
{
key: "name",
value: function name() {
return "".concat(this.first, " ").concat(this.last);
}
}
]);
return Person;
})()),
_temp)),
(_descriptor = _applyDecoratedDescriptor(_class.prototype, "xxx", [readonly], {
configurable: true,
enumerable: true,
writable: true,
initializer: function initializer() {
return 1;
}
})),
_class);
var p = new Person();
console.log(p);
首先可以看到,修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。
我们看转化后的ES5代码,有两个实例属性、原型属性,但只是修饰了用@
运算符后边的xxx
属性,yyy
和name
都以正常的方式创建在了其各自的位置上。
我们再比较一下修饰原型属性
源代码
class Person {
@nonenumerable
get kidCount() { return this.children.length; }
}
function nonenumerable(target, name, descriptor) {
descriptor.enumerable = false;
return descriptor;
}
编译后的代码
// 原型属性
_applyDecoratedDescriptor(
_class.prototype,
"kidCount",
[nonenumerable],
Object.getOwnPropertyDescriptor(_class.prototype, "kidCount"),
_class.prototype
)
// 实例属性
(_descriptor = _applyDecoratedDescriptor(_class.prototype, "xxx", [readonly], {
configurable: true,
enumerable: true,
writable: true,
initializer: function initializer() {
return 1;
}
}))
可以看到,修饰原型上的属性时,是用Object. getOwnPropertyDescriptor去取其描述符,在这基础上进行修改。而修饰实例属性的时候不是,这是因为在处理修饰器的时候还没有实例产生呀,只能自己写一个。
注意点
普通函数不能用修饰器的原因是函数存在变量提升。
在redux中的应用
// 原写法
eactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
//用装饰器的写法
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
connect(mapStateToProps, mapDispatchToProps)
生成的函数会执行,参数就是MyReactComponent
这个类。