与 'this' 相比,'super' 的值如何确定?

How is the value of 'super' determined compared to 'this'?

this 由执行上下文决定

我习惯了JavaScript中this的特殊性。在下面的示例中,this 由执行上下文确定。尽管 getProtoPropViaThis 函数是在 x 上定义的,但 this 的值取决于函数的调用方式:

const xProto = {
  protoProp: "x",
};

const x = {
  getProtoPropViaThis() {
    return this.protoProp;
  },
}
Object.setPrototypeOf(x, xProto);

const yProto = {
  protoProp: "y",
};

const y = {
  getProtoPropViaThis: x.getProtoPropViaThis,
}
Object.setPrototypeOf(y, yProto);

console.log( x.getProtoPropViaThis() ); // Output: x
console.log( y.getProtoPropViaThis() ); // Output: y

super不受执行上下文影响?

我已经使用 super 一段时间了,但总是在 类 的上下文中。所以我最近读了好几遍 an article that demonstrated, tangentially, that super does not appear to follow the same rules as this. Somehow, in a way I don't fully understand (despite reading the ECMAScript 2021 language docs 时感到很惊讶),super 设法坚持了它的原始参考:

const xProto = {
  protoProp: "x",
};

const x = {
  getProtoPropViaSuper() {
    return super.protoProp;
  },
}
Object.setPrototypeOf(x, xProto);

const yProto = {
  protoProp: "y",
};

const y = {
  getProtoPropViaSuper: x.getProtoPropViaSuper,
}
Object.setPrototypeOf(y, yProto);

console.log( x.getProtoPropViaSuper() ); // Output: x
console.log( y.getProtoPropViaSuper() ); // Output: x

请注意 y 在其原型链中的任何地方都没有 xxProto,但它仍然是 xProto 的 属性在调用 y.getProtoPropViaSuper()

时访问

差异明显 Object.assign

再举个例子,以下根本不起作用:

const xProto = {
  protoProp: "x",
};

const x = Object.assign(Object.create(xProto), {
  getProtoPropViaSuper() {
    return super.protoProp;
  },
});

console.log(x.getProtoPropViaSuper());

super的值是Object的原型,不是x的,所以上面的例子只是打印undefined.

遵循文档

12.3.7.1 Runtime Semantics: Evaluation

SuperProperty :super. IdentifierName

  1. Let env be GetThisEnvironment().
  2. Let actualThis be ? env.GetThisBinding().
  3. Let propertyKey be StringValue of IdentifierName.
  4. If the code matched by this SuperProperty is strict mode code, let strict be true; else let strict be false.
  5. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict).

12.3.7.3 MakeSuperPropertyReference ( actualThis, propertyKey, strict )

The abstract operation MakeSuperPropertyReference takes arguments actualThis, propertyKey, and strict. It performs the following steps when called:

  1. Let env be GetThisEnvironment().
  2. Assert: env.HasSuperBinding() is true.
  3. Let baseValue be ? env.GetSuperBase().
  4. Let bv be ? RequireObjectCoercible(baseValue).
  5. Return a value of type Reference that is a Super Reference whose base value component is bv, whose referenced name component is propertyKey, whose thisValue component is actualThis, and whose strict reference flag is strict.

8.1.1.3.5 GetSuperBase ( )

  1. Let envRec be the function Environment Record for which the method was invoked.
  2. Let home be envRec.[[HomeObject]].
  3. If home has the value undefined, return undefined.
  4. Assert: Type(home) is Object.
  5. Return ? home.[GetPrototypeOf].

最后:

8.1.1.3 Function Environment Records, Table 17

[[HomeObject]] : If the associated function has super property accesses and is not an ArrowFunction, [[HomeObject]] is the object that the function is bound to as a method. The default value for [[HomeObject]] is undefined.

按照此文档,似乎 getProtoPropViaSuper 应该作为一种方法绑定到 y,但也许在创建和保留 x 对象时以某种方式存储了绑定即使将函数分配给 y。但是,我无法从这些文档中解析发生的时间和地点。

如果有人能用通俗易懂的语言解释这种行为,我将不胜感激。 super 如何确定它的值?它如何保持其原始 super 上下文?如果它持有原始对象,这似乎会导致内存泄漏,因为原始对象无法被垃圾回收。但也许 super 引用是在准编译时确定的? (我说“准”是因为引用仍然可以被Object.setPrototypeOf改变)

perhaps that binding is somehow stored when the x object is created and retained even when the function is assigned to y.

是的,正是这样。 getProtoPropViaSuper 方法基本上关闭了定义它的对象。这存储在函数本身的内部 [[HomeObject]] 槽中,这就是它被保留的原因如果您将该方法分配给不同的对象,或者 - 更重要的是 - 在不同的对象上继承它1。对于对象字面量中的方法定义,它是字面量创建的对象;对于 classes 中的方法定义,它是 class 的 .prototype 对象。

1:为什么它需要是静态引用而不是像 Object.getPrototypeOf(this) 这样的 invocation-dependent,参见 here.

If it's holding onto the original object, it seems like that could cause memory leaks as the original object could not be garbage collected.

不,它不会导致比其他闭包更多的内存泄漏。当然,该方法阻止了它的 home 对象被垃圾收集,但是考虑到 home 对象是一个原型对象——至少在正常使用中——它也在通常调用该方法的对象的原型链中被引用,这不是问题。

I would really appreciate it if someone could explain this behavior in plain language. How does super determine its value?

它采用其绑定的 home 对象的原型,该对象是定义该方法的对象。但是请注意,在 super 上访问 属性 不会 return对该对象的 属性 的普通引用,但在调用(方法)时将采用当前作用域的 this 值作为方法调用的 this 参数的特殊引用,而不是原型对象。简而言之,

const x = {
  method() {
    super.something(a, b);
  }
}

脱糖为

const x = {
  method() {
    Object.getPrototypeOf(x).something.call(this, a, b);
  }
}

class X {
  method() {
    super.something(a, b);
  }
}

脱糖为

class X {
  method() {
    Object.getPrototypeOf(X.prototype).something.call(this, a, b);
  }
}