该篇文章参考以下博文,本文只用于记录学习
https://segmentfault.com/a/1190000015565616
ES6中的class,继承,super
ES6中的extends原理
class的继承语法源于寄生组合继承,下方代码是通过babel转化的部分class继承
//转化前
class A extends B {}
//转化后
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {//寄生继承关键
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;//原型链继承关键
return o;
};
return _setPrototypeOf(o, p);
}
var Rabbit = /*#__PURE__*/ function(_Animal) {
_inherits(Rabbit, _Animal);
var _super = _createSuper(Rabbit);
function Rabbit() {
_classCallCheck(this, Rabbit);
return _super.apply(this, arguments);//组合继承关键
}
return Rabbit;
}(Animal);
原型链继承的关键在于,改变子构造函数原型的__prototype__
指向,,这样通过子构造函数new的实例可以访问到Animal
的方法
extends的使用方法
- 第一种,直接继承类
class Rabbit extends Animal {}
- 第二种,可以跟经过函数处理后的类
function Animal(behavior) {
return class {
run() { alert(behavior) }
}
}
class Rabbit extends Animal("I can run") {}
new User().run(); // I can run
上面方法适用于,当类需要条件生成时,很方便。
使用父组件方法
继承,自然是为了可以使用父当中的方法,或者继承属性,不然费这么大的劲儿干啥
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run (speed) {
this.speed += speed;
debugger
alert(`${this.name} runs with speed ${this.speed}.`);
}
}
class Rabbit extends Animal {
jump() {
alert(`${this.name} jump`);
}
sports () {
super.run(5);
this.jump();
}
}
let rabbit = new Rabbit('White Rabbit');
rabbit.run(10); //White Rabbit runs with speed 10.
rabbit.sports(); //White Rabbit runs with speed 15.White Rabbit jump
箭头函数中可以从外部访问到super
正常来讲,由于super是父类提供给子类使用父方法的媒介,在一般函数中因为this的指向会指向调用它的那个对象,所以无法使用super。
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed += speed;
}
}
class Rabbit extends Animal {
jump() {
console.log(`${this.name} jump`);
console.log('jump中的this', this); //Rabbit
setTimeout(function () {
console.log('function中的this', this)//Window
super.run(5) //报错,由于this指向改变,无法获取到Rabbit,进而无法获取到Animal
}, 1000)
setTimeout(() => {
console.log('箭头function中的this', this)//Rabbit
}, 1000)
}
sports() {
super.run(5);
this.jump();
}
}
let rabbit = new Rabbit('White Rabbit');
rabbit.sports();
子类的构造函数
子类的constructor
在添加新的属性的时候,如果直接添加,会报错this
没有定义
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
// ...
}
class Rabbit extends Animal {
constructor(name, earLength) {
this.speed = 0;
this.name = name;
this.earLength = earLength;
}
// ...
}
let rabbit = new Rabbit("White Rabbit", 10);// Error: this is not defined.
上面代码中,因为通过继承得来的构造函数,内部会被标记为
[[ConstructorKind]]:“derived”
构造函数类型:派生的
而派生的构造函数内部的this
不会被创建,而是希望父类来创建。这个时候就需要调用super()
来帮助子类创建一个构造函数,而且创建必须发生在使用this
之前。
Super的实现原理
Super的实现不同于我们现在已知的任何方法,当一个对象运行的时候,将当前对象作为this
let animal = {
name: "Animal",
eat() {
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit",
eat() {
// call改变了this的指向,如果不加call,那么最终输出将为Animal eats.而不是下面的Rabbit eats.
this.__proto__.eat.call(this); // (*)
}
};
rabbit.eat(); // Rabbit eats.
上面的(*)
如果不理解,可以看一下这篇文章里面的第4点
JavaScript各种继承,原型继承,构造函数继承,组合继承,寄生组合,ES6继承,我能学会你也可以
上面的构造函数继承理解后,接下来我们追加一个继承,
let animal = {
name: "Animal",
eat() {
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
eat() {
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
this.__proto__.eat.call(this); // (**)
}
};
longEar.eat(); // Error: Maximum call stack size exceeded 超过最大调用堆栈
一般来说,如果报了上面那个错,基本上就是陷入死循环或者回调地狱了。
具体原因我们一起来看一下,跟踪一下longEar.eat()
,
- 首先
longEar.eat()
中,this.__proto__
代表的是rabbit
,后面的this
代表的是longEar
- 然后在
rabbit
中,this.__proto__
代表的也是rabbit
,后面的this
代表的也是longEar
(call改变了this指向,让父构造函数中的this变为子中的this) - 再然后,rabbit中,的eat就变成了下方这个样子
let rabbit = {
__proto__: animal,
eat() {
rabbit.eat.call(longEar); //这里是为了理解做了个转化,正常代码不能这样写
}
};
上面代码先不看call(longEar)
,只看rabbit.eat
的话,如下
let rabbit = {
__proto__: animal,
eat() {
rabbit.eat()//这里出现了一个自调用,导致eat无限调用自己,陷入循环
}
};
上面代码陷入循环不要紧,还有call,说不定call可以帮助脱离循环。
我们知道call是可以改变this的指向,那改变了this后,循环自身执行eat的时候,rabbit (this._ _proto__)
里面跟的this被call改变成了longEar,所以执行的还是rabbit.eat,并且无限循环下去,导致崩溃
盗用一张图片说明下:
1.在 longEar.eat()
里面,(**)
行中调用了 rabbit.eat
,并且this = longEar
。
2.然后在rabbit.eat的 (*)
行中,我们希望传到原型链的下一层,但是this = longEar
,所以 this .__ proto __.eat
又是rabbit.eat
!
[[HomeObject]]
上面的问题,不能单纯的通过this来解决,还需要还需要一个特殊的内部属性[[HomeObject]]
当函数被指定为类或对象方法时,其 [[HomeObject]] 属性为该对象。
通过这个内部属性,我们就可以记住this,而不会丢失
let animal = {
name: "Animal",
eat() { // [[HomeObject]] == animal
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit",
eat() { // [[HomeObject]] == rabbit
super.eat();
}
};
let longEar = {
__proto__: rabbit,
name: "Long Ear",
eat() { // [[HomeObject]] == longEar
super.eat();
}
};
longEar.eat(); // Long Ear eats.
整个super的实现就完成了,有兴趣的同学可以尝试一下用babel把super转化成commonJS,也很有趣。
下面是继承过程中使用super,转化为commonJS后的部分源码
function _createSuper(Derived) { //创建super,入参Derived是子类,其中子类中的constructor是派生而来
return function () {
var Super = _getPrototypeOf(Derived), result;//获取子类构造函数的原型
if (_isNativeReflectConstruct()) { //判断是否为自有属性构造,通过上面我们可知,类的继承属于派生构造
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
}
else { result = Super.apply(this, arguments); }
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) { //可能的构造返回值
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) { //已初始化
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () { }));
return true;
} catch (e) { return false; }
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf :
function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
var Rabbit = /*#__PURE__*/function (_Animal) {
_inherits(Rabbit, _Animal);
var _super = _createSuper(Rabbit); //创建了super
function Rabbit(age) {
var _this;
_classCallCheck(this, Rabbit);
_this = _super.call(this); //通过super来记录this,并赋值给子类的this
_this.gae = age;
return _this;
}
return Rabbit;
}(Animal);