super既可以当做函数使用,也可以当做对象使用,两种使用的时候完全不一样。
函数用时: 使用super(...)调用父构造函数(仅在constructor函数中)
在constructor中必须调用super方法,因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,super就代表了父类的构造函数。
super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此 super()在这里相当于A.prototype.constructor.call(this, props)
在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的 this指向的是B。
当做对象使用: 在普通方法中指向父类的原型对象,使用super.method(...)调用父方法;在静态方法中指向父类。
在子类的方法中super.print();指向的是父类原型上的方法。
因为super的两种用法,所以es6规定必须要明确使用方式,像console.log(super)就会报错。
当一个对象方法运行时,它将当前对象作为this。如果调用super.method()那么如何检索method?容易想到,需要从当前对象的原型中取出method。
也许可以从this的[[Prototype]]中获得方法,就像this .__ proto __.method一样?不幸的是,这是行不通的。
这里,rabbit.eat()调用父对象的animal.eat()方法:
let animal = {
name: "Animal",
eat() {
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
name: "Rabbit",
eat() {
// that's how super.eat() could presumably work
this.__proto__.eat.call(this); // (*)
}
};
rabbit.eat(); // Rabbit eats.
在(*)这一行,从原型animal中取出eat,并以当前对象的上下文中调用它。注意.call(this)在这里很重要,因为只写this .__ proto __.eat()的话,eat的调用对象将会是animal,而不是当前对象。以上代码的alert是正确的。
但是再添加一个对象到原型链中,就要出事了:
let animal = {
name: "Animal",
eat() {
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
eat() {
// ...bounce around rabbit-style and call parent (animal) method
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
// ...do something with long ears and call parent (rabbit) method
this.__proto__.eat.call(this); // (**)
}
};
longEar.eat(); // Error: Maximum call stack size exceeded
这原因一眼可能看不透,但如果跟踪longEar.eat()调用,大概就知道为什么了。
在(*)和(**)两行中, this的值是当前对象(longEar)。重点来了:所有方法都将当前对象作为this,而不是原型或其他东西。
因此在两行(*)和(**)中this.__ proto__的值都是rabbit,他们都调用了rabbit.eat,于是就这么无限循环下去。情况如图:

1.在longEar.eat() 里面,(**)行中调用了rabbit.eat,并且this=longEar。
// inside longEar.eat() we have this = longEar
this.__proto__.eat.call(this) // (**)
// becomes
longEar.__proto__.eat.call(this)
// that is
rabbit.eat.call(this);
2.然后在rabbit.eat的(*)行中希望传到原型链的下一层,但是this=longEar,所以this.__proto__.eat又是rabbit.eat!
// inside rabbit.eat() we also have this = longEar
this.__proto__.eat.call(this) // (*)
// becomes
longEar.__proto__.eat.call(this)
// or (again)
rabbit.eat.call(this);
因此rabbit.eat在无尽循环调动,无法进入下一层。这个问题不能简单使用this解决。
为了提供解决方案,JavaScript为函数添加了一个特殊的内部属性:[[HomeObject]]
[[HomeObject]]:当函数被指定为类或对象方法时,其[[HomeObject]]属性为该对象。
这实际上违反了unbind函数的思想,因为方法记住了它们的对象,并且[[HomeObject]]不能被改变,所以这是永久bind(绑定)。
在JavaScript这是一个很大的变化,但是这种改变是安全的,[[HomeObject]]仅用于在super中获取下一层原型,所以它不会破坏兼容性。
来看看它是如何在super中运作的:
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.
每个方法都会在内部[[HomeObject]]属性中记住它的对象,然后super使用它来解析原型。
在类和普通对象中定义的方法中都定义了[[HomeObject]],必须使用method()而不是"method: function()"。使用非方法语法(non-method syntax)没有设置[[HomeObject]]属性,继承也不起作用:
let animal = {
eat: function() { // should be the short syntax: eat() {...}
// ...
}
};
let rabbit = {
__proto__: animal,
eat: function() {
super.eat();
}
};
rabbit.eat(); // Error calling super (because there's no [[HomeObject]])
网友评论