本篇共3个章节。babel
前2个章节介绍class中两种方式定义方法的不一样、decorator如何做用于class的方法。app
最后1个章节经过一个demo介绍了如何实现一个兼容class普通方法和class属性方法的装饰器,以及如何保留装饰器装饰的箭头函数式中this为类实例的特性。函数
在React中的函数中固定this指向组件实例是一个常见的需求,一般有如下三种写法:this
1.在constructor中使用bind指定this:spa
this.handlePress = this.handlePress.bind(this)
复制代码
2.使用autobind的装饰器:prototype
@autobind
handlePress(){}
复制代码
3.使用class properties与arrow functioncode
handlePress = () => {}
复制代码
这里有两种为类声明方法的方式,第一种如一、2在类中直接声明方法,第二种为将方法声明为类的一个属性(’=‘标识)。对象
咱们都知道class即function,让咱们定义一个简单的类,观察babel编译后的结果,看看这两种方式声明的方法有何不一样。ip
class A {
sayHello() {
}
sayWorld = function() {
}
}
复制代码
编译后原型链
var A = function () {
function A() {
_classCallCheck(this, A);
this.sayWorld = function () {};
}
_createClass(A, [{
key: "sayHello",
value: function sayHello() {}
}]);
return A;
}();
复制代码
编译后的代码中sayHello和sayWorld是经过不一样方式关联到A上的。sayWorld的定义发生在构造函数执行期间,即类实例的建立时。而sayHello是经过_createClass方法关联到A上的。
来看看_createClass作了什么:
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
// 建立一个数据属性,并将其定义在target对象上
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);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
复制代码
_createClass中建立了一个以下的数据属性,使用Object.defineProperty定义在A.prototype上。
{
enumerable: false,
configurable: true,
writable: true,
value: function sayHello() {}
}
复制代码
可见sayHello方法是定义在A.prototype上的方法,会被众多A的实例所共享;而sayWorld则是每一个A实例独有的方法(每次建立实例都会新建)。
一、普通的类方法实际归属于class.prototype,该类的众多实例将经过原型链共享该方法。
二、属性方式定义的类方法归属于class的实例,同名方法在类的不一样实例中并不相同。
让咱们对A作一些修改,从新编译。
class A {
sayHello() {
console.log('hello', this);
}
sayWorld = function() {
console.log('world', this);
}
sayName = () => {
console.log('name', this);
}
}
复制代码
编译后
var A = function () {
function A() {
var _this = this;
_classCallCheck(this, A);
this.sayWorld = function () {
console.log('world', this);
};
this.sayName = function () {
console.log('name', _this);
};
}
_createClass(A, [{
key: 'sayHello',
value: function sayHello() {
console.log('hello', this);
}
}]);
return A;
}();
复制代码
咱们都知道箭头函数中this的指向为其声明时当前做用域的this,因此sayName中的this在编译过程当中被替换为_this(构造函数执行时的this,即类实例自己),这就是前面固定方法this指向实例的第三种方法"使用class properties与arrow function"生效的缘由。
装饰器(decorator)是一个函数,用于改造类与类的方法。篇幅缘由咱们这里只介绍做用于类方法的装饰器。一个简单的函数装饰器构造以下:
function decoratorA(target, name, descriptor) {
// 未作任何修改
}
复制代码
target为class.prototype。
name即方法名称。
descriptor有两种,数据属性和访问器属性。两种属性包含了6种特性,enumerable和configurable为共有的2种特性,writable和value为数据属性独有,而getter和setter为访问器属性独有。
看一个简单的例子:
function decoratorA() {}
function decoratorB() {}
class A {
@decoratorA
@decoratorB
sayHello() {
}
}
复制代码
编译后
function decoratorA() {}
function decoratorB() {}
var A = (_class = function () {
function A() {
_classCallCheck(this, A);
}
_createClass(A, [{
key: "sayHello",
value: function sayHello() {}
}]);
return A;
}(), (_applyDecoratedDescriptor(_class.prototype, "sayHello", [decoratorA, decoratorB], Object.getOwnPropertyDescriptor(_class.prototype, "sayHello"), _class.prototype)), _class);
复制代码
与以前同样sayHello定义为A.prototype的属性,然后执行_applyDecoratedDescriptor应用装饰器decoratorA和decoratorB。
来看看_applyDecoratedDescriptor作了什么:
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;
}
// 以上为初始化一个数据属性(initializer不属于上文提到的6种属性特性,第三节详述其做用)
// 本例中此处desc为{ enumerable: false, configurable: true, writable: true, value: function sayHello() {} }
// 此处的reverse代表装饰器将按照距离sayHello由近及远的顺序执行,即先应用decoratorB再应用decoratorA
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
// 装饰器执行,可在装饰器内部按需修改desc
return decorator(target, property, desc) || desc;
}, desc);
// 本例中无initializer不执行此段代码
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
// 将装饰器处理后的desc定义到target即A.prototype上
Object['define' + 'Property'](target, property, desc);
desc = null;
}
// 返回null
return desc;
}
复制代码
经过上述代码分析咱们认识到:
一、装饰器的执行发生在类建立后,此时并没有实例
二、依照距离函数由近及远执行
三、经过修改被装饰方法的属性特性,能够实现咱们所需的功能(例如autobind-decorator实现绑定this)。
decorator是es7归入规范的js特性,而class properties目前是stage3阶段(截止2018.11.23)的提案,尚未正式归入ECMAScript。
一个属性方法的特色是其建立在实例生成阶段(构造函数中),而装饰器的执行是在类建立后(实例生成前),这里就发生了一个概念上的小冲突,装饰器执行时属性方法彷佛还没建立。那装饰器是如何装饰一个属性方法的呢,让咱们到代码中找出答案。
function decoratorA() {}
class A {
@decoratorA
sayName = () => {
console.log(this);
}
}
复制代码
编译后
function _initDefineProp(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
});
}
function decoratorA() {}
var A = (_class = function A() {
_classCallCheck(this, A);
_initDefineProp(this, "sayName", _descriptor, this);
}, (_descriptor = _applyDecoratedDescriptor(_class.prototype, "sayName", [decoratorA], {
enumerable: true,
initializer: function initializer() {
var _this = this;
return function () {
console.log(_this);
};
}
})), _class);
复制代码
一、经过initializer来记录并标识类的属性方法
二、_applyDecoratedDescriptor建立返回了一个属性的描述对象_descriptor
三、在构造函数中经过_initDefineProp将_descriptor定义到实例this上(属性方法依然归属于实例,而不是class.prototype)
一、_applyDecoratedDescriptor需返回一个包含initializer的descriptor,以确保属性的value是经过initializer调用初始化
二、装饰器在处理descriptor时,返回的descriptor需包含initializer,而不是数据属性或访问器属性格式的descriptor.
需求:检查登陆状态的装饰器,当装饰器修饰的方法调用时,检查登陆状态。若已登陆则执行该方法,若未登陆,则执行一个指定方法提示需登陆。
// 登陆状态
let logined = true;
function checkLoginStatus() {
return new Promise((resolve) => {
resolve(logined);
// 每次返回登陆状态后对登陆状态取反
logined = !logined;
});
}
// 提示须要登陆
function notice(target, tag) {
console.log(tag, this === target, 'Need Login!');
}
// 检查登陆状态的装饰器
function checkLogin(notLoginCallback) {
return function decorator(target, name, descriptor) {
// 方法为类属性方法
if (descriptor.initializer) {
const replaceInitializer = function replaceInitializer() {
const that = this;
// 此处传入了指向类实例的this
const fn = descriptor.initializer.call(that);
return function replaceFn(...args) {
checkLoginStatus().then((login) => {
if (login) {
return fn.call(this, ...args);
}
return notLoginCallback.call(this, ...args);
});
};
};
return {
enumerable: true,
configurable: true,
writable: true,
initializer: replaceInitializer,
};
}
// 普通的类方法
const originFn = descriptor.value;
const replaceFn = function replaceFn(...args) {
const that = this;
checkLoginStatus().then((login) => {
if (login) {
return originFn.call(that, ...args);
}
return notLoginCallback.call(that, ...args);
});
};
return {
enumerable: true,
configurable: true,
writable: true,
value: replaceFn,
};
}
}
class A {
constructor() {
this.printA2 = this.printA2.bind(this);
}
printA1(target, tag) {
console.log(tag, this === target);
}
@checkLogin(notice)
printA2(target, tag) {
console.log(tag, this === target);
}
printB1 = function(target, tag) {
console.log(tag, this === target);
}
@checkLogin(notice)
printB2 = function(target, tag) {
console.log(tag, this === target);
}
printC1 = (target, tag) => {
console.log(tag, this === target);
}
@checkLogin(notice)
printC2 = (target, tag) => {
console.log(tag, this === target);
}
}
const a = new A();
a.printA1(a, 1); // 1 true
(0, a.printA1)(a, 2); // 2 false
a.printA2(a, 3); // 3 true
(0, a.printA2)(a, 4); // 4 true 'Need Login!'
a.printB1(a, 5); // 5 true
(0, a.printB1)(a, 6); // 6 false
a.printB2(a, 7); // 7 true
(0, a.printB2)(a, 8); // 8 false 'Need Login!'
a.printC1(a, 9); // 9 true
(0, a.printC1)(a, 10); // 10 true
a.printC2(a, 11); // 11 true
(0, a.printC2)(a, 12); // 12 true 'Need Login!'
复制代码
一、应用了checkLogin装饰器的普通类方法printA2可使用bind绑定this指向a。
二、箭头函数this均保持指向了实例a。
三、应用了checkLogin装饰器的方法连续两次调用输出的登陆状态相反,符合预期的装饰器效果。
若是读到这里,但愿你能有所收获~