ES6继承:使用`super`访问父类的属性

Javascript的super关键字,当我在Chrome、Babel、TypeScript上运行代码时,得到不同的结果。

我的问题是哪个结果是正确的?规范的哪个部分定义了这种行为?

以下代码:

class Point {
  getX() {
    console.log(this.x); // C
  }
}

class ColorPoint extends Point {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(this.x)   // A
    console.log(super.x)  // B
  }
  
  m() {
    this.getX()
  }
}

const cp = new ColorPoint();
cp.m();

结果:

  • Chrome 58.0.3029.110 64位(V8 5.8.283.38)
  • 巴别塔代表6.24.2
  • 打字稿2.3

链接:

  • gist
  • babel

解决方案

简明答案:

Chrome是正确的。这是由于get和set之间的不平衡造成的。

OrdinarySet是reciever敏感的,但OrdinaryGet不敏感。

sosuper.x = 3具有与this.x = 3相同的效果,因为这里的接收方是this。评估永远不会达到thissuper.x将始终得到undefined,因为A.prototype没有这样的字段。


更多详细信息:

super.x是SuperReference。而对SuperReference的赋值将调用PutValue(V, W),进而调用super对象的内部槽[[Set]],最后调用OrdinarySet

在纯JavaScript中,super.x = 3语句基本等同于:

OrdinarySet(proto, 'x', 3, this)

其中proto是超级对象,内部是构造函数ColorPoint[[HomeObject]]proto等同于Object.create(Point.prototype),如ClassDefinitionEvaluation所指定,作为[[HomeObject]]传递给构造函数。


现在让我们看看OrdinarySet是如何工作的。在步骤4c和4d中,规范要求在接收方this而不是proto对象上执行设置操作。

是否将ExistingDescriptor设置为?接收方。[GetOwnProperty]。

如果ExistingDescriptor不是未定义的,则

如果IsAccessorDescriptor(ExistingDescriptor)为true,则返回false。

如果ExistingDescriptor.[[Writable]]为False,则返回False。

让valueDesc为PropertyDescriptor{[[value]]:v}。

返回?接收方.[[DefineOwnProperty]](P,valueDesc).

这些语句表示OrdinarySet(proto, 3, this)表示this.x = 3


另一方面,OrdinaryGet忽略Receiversuper.x

OrdinaryGet(proto, 'x', this)

OrdinaryGet子句中根本没有Receiver!所以super.x等同于Object.create(Point.prototype).x,当然就是undefined

经验法则是,如果代码转换程序和浏览器之间存在差异,浏览器(尤其是Chrome)通常更忠于ECMAScript规范。转换程序通常会牺牲一些边缘情况的正确性来换取运行时效率。

相关文章