【问题标题】:How is the value of 'super' determined compared to 'this'?与“this”相比,“super”的值如何确定?
【发布时间】:2020-10-16 19:17:44
【问题描述】:

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 时,我感到很惊讶,这表明super 似乎没有遵循与this 相同的规则。不知何故,以某种我不完全理解的方式(尽管多次阅读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 在其原型链中的任何位置都没有x 或xProto,但在调用y.getProtoPropViaSuper() 时仍会访问xProto 的属性

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. 设 env 为 GetThisEnvironment()。
  2. 让实际这是? env.GetThisBinding()。
  3. 令 propertyKey 为 IdentifierName 的 StringValue。
  4. 如果这个SuperProperty匹配的代码是严格模式代码,让strict为真;否则让 strict 为假。
  5. 返回 ? MakeSuperPropertyReference(actualThis, propertyKey, strict)。

12.3.7.3 MakeSuperPropertyReference ( actualThis, propertyKey, strict )

抽象操作 MakeSuperPropertyReference 接受参数 actualThis、propertyKey 和 strict。调用时会执行以下步骤:

  1. 设 env 为 GetThisEnvironment()。
  2. 断言:env.HasSuperBinding() 为真。
  3. 设 baseValue 为 ? env.GetSuperBase().
  4. 让 bv 成为? RequireObjectCoercible(baseValue)。
  5. 返回一个 Reference 类型的值,它是一个 Super Reference,其基值组件为 bv,其引用名称组件为 propertyKey,其 thisValue 组件为 actualThis,其严格引用标志为 strict。

8.1.1.3.5 GetSuperBase ( )

  1. 设 envRec 为调用方法的函数 Environment Record。
  2. 让 home 为 envRec.[[HomeObject]]。
  3. 如果 home 的值为 undefined,则返回 undefined。
  4. 断言:Type(home) 是 Object。
  5. 返回 ?家。[GetPrototypeOf]。

最后:

8.1.1.3 Function Environment Records,Table 17

[[HomeObject]] : 如果关联函数具有超属性访问且不是 ArrowFunction,则 [[HomeObject]] 是该函数作为方法绑定到的对象。 [[HomeObject]] 的默认值未定义。

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

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

【问题讨论】:

  • 不要使用__proto__,因为它是deprecated
  • 谢谢,我知道了。以这种方式演示更容易。问题不在于原型
  • @limido 重写以避免__proto__ 用法。不确定它是否对这个问题有所帮助,但希望它现在不会分散注意力。
  • 我开始认为这可能是 Chromium 错误,但我在 Firefox 和 Safari 中仔细检查并得到相同的行为,所以我猜这是符合标准的。我只是不明白标准。
  • 从您现在添加的链接中:“由于 super 在 [[HomeObject]].[[Prototype]] 中查找父方法,这意味着它搜索 sayHiMixin.[[原型]],而不是用户。[[原型]]。”另见this question referencing specs

标签: javascript prototype super


【解决方案1】:

也许在创建 x 对象时以某种方式存储绑定并保留,即使函数分配给 y 也是如此。

是的,这正是发生的事情。 getProtoPropViaSuper 方法基本上关闭了定义它的对象。它存储在函数本身的内部 [[HomeObject]] 槽中,这就是为什么如果你分配方法会保留它到不同的对象,或者 - 更重要的是 - 在不同的对象上继承它1。对于对象字面量中的方法定义,它是由字面量创建的对象;对于classes 中的方法定义,它是类的.prototype 对象。

1:关于为什么它需要是静态引用而不是像 Object.getPrototypeOf(this) 这样依赖于调用的东西,请参阅 here。

如果它持有原始对象,这似乎可能导致内存泄漏,因为原始对象无法被垃圾收集。

不,它不会导致比其他闭包更多的内存泄漏。当然,该方法可以防止其 home 对象被垃圾回收,但鉴于 home 对象是一个原型对象 - 至少在正常使用中 - 它也在通常调用该方法的对象的原型链中被引用,这不是问题。

如果有人能用通俗易懂的语言解释这种行为,我将不胜感激。 super如何确定它的价值?

它采用其绑定的主对象的原型,这是定义该方法的对象。但是请注意,访问super 上的属性不会返回对该对象属性的普通引用,而是返回一个调用(方法)时的特殊引用将当前作用域的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);
  }
}

【讨论】:

  • 谢谢。到目前为止,我阅读的所有文档都使用一些模糊的手波语言来说 super 转到对象的原型,但显然有一个警告......它转到对象的原型最初定义的函数。此外,关于this 的额外信息是一个有用的标注。有趣的是Object.setPrototypeOf 将更新[[HomeObject]] 参考。我想这是功能如此缓慢的部分原因。
  • @JDBstillremembersMonica 不,它不会更新函数的 [[HomeObject]],但鉴于 super 会查找 [ 的原型[HomeObject]] 你当然可以通过改变 [[HomeObject]] 的原型来改变你会得到什么。
  • @JDBstillremembersMonica 原来是designed to be more flexible 甚至……
  • Exposing HomeObject 是对 Chromium 如何实际存储 [[HomeObject]] 的引人入胜的深入探讨......它是函数上的隐藏符号属性。稍微修改一下源代码,您就可以将其公开。很整洁。
猜你喜欢
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-22
  • 1970-01-01
  • 2013-05-26
相关资源
最近更新 更多