深入理解class和装饰器(上)
深入理解class和装饰器
class 的出现大大简化了 javascript 中类的写法,而装饰器又是 class 里面非常实用的功能,但是老实说,它们都是语法糖,并没有引入新的功能,那它们的原理是怎样的呢?本文来一一探究。通过本文,您可以学到:
- class 语法糖的原理是什么?
- super 的原理是什么?有什么注意事项?
- 装饰器的原理是什么?
- vue-class-component 是怎么实现 vue 的 class 写法的?
- vue-property-decorator 是怎么实现 watch 装饰器的?
class 语法糖
我们首先用 class 的写法写一个 demo:
class A {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name);
}
static move() {
console.log('move');
}
}
class B extends A {
constructor() {
this.a = 1;
super();
this.b = 2;
}
hello() {
console.log('hello');
}
static go() {
console.log('go');
}
}
通过分析 babel 打包后的代码,其实可以简化成下面这样:
var A = /*#__PURE__*/function () {
function A(name) {
this.name = name
}
A.prototype.say = function say() {
console.log(this.name)
}
A.move = function move() {
console.log('move')
}
return A
}()
var B = /*#__PURE__*/function (_A) {
var _super = function _createSuperInternal() {
return _A.apply(this, arguments) || this
}
function B() {
var _this;
// _this.a = 1; // 这里会出现 _this 未定义,导致报错,所以不能在 super 之前绑定实例属性
_this = _super.call(this)
console.log(_this)
_this.b = 2;
return _this;
}
// 这里其实等价于:
// B.prototype = Object.create(_A.prototype)
// B.prototype.constructor = B
B.prototype = Object.create(_A.prototype, {
constructor: {
value: B,
writable: true,
configurable: true
}
})
B.prototype.hello = function hello() {
console.log('hello');
}
B.go = function go() {
console.log('go');
}
return B;
}(A);
可以看到:
- class 语法糖原理其实就是使用 constructor 作为构造函数,然后在构造函数的 prototype 上面绑定实例方法,并且直接在构造函数上面绑定静态方法。
- class 的继承就是组合继承的形式。
关于 super
我们从上面可以看到,super 其实是内部创建的一个方法,它使用构造函数继承的方法来继承实例属性。但由于 _this 其实是由 super 返回的,所以如果在 super 之前绑定实例属性的话,_this 还未定义,导致报错。所以在 super 之前不能绑定实例属性。
那这里的 _this 为什么不直接用自己的 this 呢?
我们来思考这样一种场景,就是构造函数里面有返回值:
class C {
constructor() {
return {a:2}
}
}
上面的类会被编译成:
var C = /*#__PURE__*/function () {
function C(name) {
return {a:2}
}
return C
}()
我们在实例化这个类的时候,其实得到的是构造函数的返回值,即{a:2}
这个对象。所以如果父类是这种返回对象的形式的话,子类在继承的时候,就必须在这个返回值上面绑定实例属性,而不是在自己的this上面绑定实例属性。