与这个相比,超级市场的价值是如何确定的?

2022-03-30 00:00:00 super javascript prototype

this由执行上下文确定

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

数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">
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设法抓住了它最初的引用:

数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">
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,但在调用y.getProtoPropViaSuper()

时仍是xProto的属性

差异明显,Object.assign

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

数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">
const xProto = {
  protoProp: "x",
};

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

console.log(x.getProtoPropViaSuper());

super的值是对象的原型,而不是x的原型,因此上面的示例仅打印undefined

跟随文档

12.3.7.1 Runtime Semantics: Evaluation

SuperProperty:超级。标识名

  1. 让env为GetThisEnvironment()。
  2. 让这成为现实吗?Env.GetThisBinding()。
  3. 将PropertyKey设置为IdentifierName的StringValue。
  4. 如果此SuperProperty匹配的代码是严格模式代码,则Strong应为True;否则,Strong应为False。
  5. 返回?MakeSuperPropertyReference(ActialThis,PropertyKey,Strong)。

12.3.7.3 MakeSuperPropertyReference ( actualThis, propertyKey, strict )

抽象操作MakeSuperPropertyReference的参数为ActualThis、PropertyKey和Strong。它在被调用时执行以下步骤:

  1. 让env为GetThisEnvironment()。
  2. Assert:env.HasSuperBinding()为True。
  3. 是否将BaseValue设置为?环境GetSuperBase().
  4. 顺其自然吗?RequireObtCoercible(BasValue)。
  5. 返回类型为Reference的值,该值是一个超引用,其基值组件为BV,引用的名称组件为PropertyKey,其thisValue组件为ActialThis,并且其严格引用标志为Struction。

8.1.1.3.5 GetSuperBase ( )

  1. 让envRec为其调用方法的函数环境记录。
  2. 让Home成为envRec.[[HomeObject]].
  3. 如果home具有未定义的值,则返回UNDefined。
  4. Assert:类型(Home)为Object。
  5. 返回?主页。[GetPrototypeOf]。

最后:

8.1.1.3 Function Environment Records,Table 17

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

按照本文档,getProtoPropViaSuper似乎应该作为一种方法绑定到y,但可能在创建x对象时以某种方式存储了该绑定,即使在将函数分配给y时也是如此。然而,我还无法从这些文档中分析这种情况在何时何地发生。

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


解决方案

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

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

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

如果它保留原始对象,则可能会导致内存泄漏,因为无法对原始对象进行垃圾回收。

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

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

它接受其绑定的home对象的原型,该对象是在其中定义方法的对象。但是请注意,访问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);
  }
}

相关文章