如何解决与“ this”相比,“ super”的价值如何确定?
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;
},};
const y = {
getProtoPropViaSuper: x.getProtoPropViaSuper,yProto);
console.log( x.getProtoPropViaSuper() ); // Output: x
console.log( y.getProtoPropViaSuper() ); // Output: x
请注意,y
在其原型链中的任何地方都没有x
或xProto
,但是调用{{ 1}}
xProto
明显的差异
对于另一个示例,以下内容根本不起作用:
y.getProtoPropViaSuper()
Object.assign
的值是Object的原型,而不是const xProto = {
protoProp: "x",};
const x = Object.assign(Object.create(xProto),{
getProtoPropViaSuper() {
return super.protoProp;
},});
console.log(x.getProtoPropViaSuper());
的原型,因此上面的示例仅打印super
。
关注文档
12.3.7.1 Runtime Semantics: Evaluation
SuperProperty :super。 IdentifierName
- 让我们成为GetThisEnvironment()。
- 实际上是这样吗? env.GetThisBinding()。
- 让propertyKey为IdentifierName的StringValue。
- 如果与此SuperProperty匹配的代码是严格模式代码,请使strict为true;否则,为true。否则,请让strict为假。
- 返回? MakeSuperPropertyReference(actualThis,propertyKey,严格)。
12.3.7.3 MakeSuperPropertyReference ( actualThis,propertyKey,strict )
抽象操作MakeSuperPropertyReference接受参数ActualThis,propertyKey和strict。调用时,它将执行以下步骤:
- 让我们成为GetThisEnvironment()。
- 声明:env.HasSuperBinding()为真。
- 让baseValue为? env。GetSuperBase()。
- 让bv成为? RequireObjectCoercible(baseValue)。
- 返回类型为Reference的值,该类型是超级引用,其基本值成分为bv,其引用名称成分为propertyKey,其thisValue成分为ActualThis,并且其严格引用标志为strict。
- 让envRec为为其调用方法的环境记录功能。
- 让我们成为envRec。[[HomeObject]]。
- 如果home的值不确定,则返回undefined。
- 声明:Type(家庭)是对象。
- 返回?主页。[GetPrototypeOf]。
最后:
8.1.1.3 Function Environment Records,Table 17
x
:如果关联的函数具有超级属性访问权限,并且不是ArrowFunction,则[[HomeObject]]是该函数作为方法绑定到的对象。 [[HomeObject]]的默认值是不确定的。
在遵循本文档之后,似乎undefined
应该作为一种方法绑定到[[HomeObject]]
,但是也许在创建getProtoPropViaSuper
对象并保留时仍然以某种方式存储该绑定。该功能已分配给y
。但是,我无法从这些文档中分析发生的时间或地点。
如果有人能用简单的语言解释这种行为,我将非常感激。 x
如何确定其价值?它看起来如何保持其原始的y
上下文?如果它保留原始对象,则可能会导致内存泄漏,因为原始对象无法被垃圾回收。但是也许super
引用是在准编译时确定的? (我之所以说“准”,是因为引用仍然可以通过super
进行更改)
解决方法
即使在
x
对象被创建和保留时,绑定也可能以某种方式存储,即使该功能已分配给y
。
是的,正是这种情况。 getProtoPropViaSuper
方法基本上会关闭定义该对象的对象。该方法存储在函数本身的内部 [[HomeObject]] 插槽中,这就是为什么在分配时将其保留的原因方法转移到另一个对象,或者-更重要的是-在另一个对象 1 上继承。对于对象常量中的方法定义,它是由常量创建的对象。对于class
es中的方法定义,它是类的.prototype
对象。
1:有关为什么它需要是静态引用而不是诸如Object.getPrototypeOf(this)
之类的依赖于调用的内容的原因,请参见here。
如果保留原始对象,则可能会导致内存泄漏,因为原始对象无法被垃圾回收。
否,它不会比其他闭包引起更多的内存泄漏。当然,该方法可以防止对其原始对象进行垃圾回收,但是考虑到该原始对象是原型对象(至少在正常情况下是这样),在通常调用该方法的对象的原型链中也引用了该原型对象,因此不是问题。
如果有人能用简单的语言解释这种行为,我将非常感激。
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,b);
}
}
,
注意: TL; DR参见super
mixin code 此答案末尾的解决方案;包括干净的方法[[Home_Object]]
。
作为Smalltalk,JavaScript和其他动态语言的(引擎/运行时)实现者,其引擎和框架[QKS,Microsoft,Smallscript] 1991年至今。重要的(引擎/运行时)实现者规则之一是,为了获得性能(功能,速度,资源消耗等)而在任何需要的地方高效欺骗实现者;但绝不要以用户层语义层所能捕捉到的方式来实现。
super
的ES6定义和实现表现出静态语言语义。不是预期的声明式/命令式JavaScript动态原型语义。因此,这可能导致对POLS的混乱理解,即使看起来不仅仅是错误的语义。
如果您不同意,请参见下面在支持 mixins 中说明的问题(以及间接引用inst-field-enumeration的问题,这些问题不需要构造实例等)。 [POLS-最不惊奇的原则]。
就目前而言,ES6 class
和super
的设计使构建自然的JS合成器和mixins具有挑战性。实际上,ES6设计迫使引擎开发人员违反“欺诈但不要被抓住”实施者规则。
我在ES6的
class
实现设计中的许多重要细节中观察到类似的实现约束决定。
正确地实施super
作为一种“有效的” Smalltalk /动态语言绑定动作,实际上是一项挑战。
超级绑定要求绑定机制找到使用{{1}引用fn-container
的{{1}} this-call
的{{1}}(方法词典) }原型链(方法词典链)。
已重定:super-binding-ctx
function
的确定在逻辑上遍历了super
原型链,以识别哪个this
包含引用{{1}的[[Home_Object]]
}。该this
成为原始“锚点”,活页夹高速缓存应从该原始“锚点”尝试绑定object
请求。这种查找需要function
谓词绑定器设计。
步骤:
- 确定正在使用
super
的当前fn-container.__proto__
- 确定哪个
super
贡献了#1的绑定力 - 从#2的
perspective-type
开始搜索function
(锚定)键。
“欺骗”是他们选择不做#1和#2。
在分类时(当super
为this...fn-container
时)将#2 硬烘焙到包含super
引用的__proto__
中。
给出class-function
,大概是ES6实现将new-ed
设置为function
。
“抓到”的结果是(由于#2是硬烤),您无法在组成原型样式的情况下获得 dynamic-super-binding mixins或没有可用的super
实现;但是在实现不存在时调用extends $class
是错误/异常。
给出:(问题示例如下所示)
[[Home_Object]]
$class.prototype
已设置为使用super
。
但是,当它运行时,其super
将不会调用class M0 {a(){}}
class M1 extends M0 {a(){super.a()}
class X {a(){}}
class Y extends X {}
Y.prototype.a = M1.prototype.a
const y = new Y
y.a()
,它将INSTEAD调用(硬编码) y.a()
。
这不是预期的动态语言(或历史M1.prototype.a
POLS或JS原始语言)行为。
也就是说,它不是Smalltalk中的
super
的动态语言语义(JS是从中衍生出来的)或其他祖先的动态语言实现设计。
因此,除其他问题外,使用X.prototype.a
和M0.prototype.a
构建mixins
需要特别注意,并且在如何编写方面具有静态语言样式约束。这些限制“出现”,违背了
super
在ES6中的灵活性。
话虽如此,我在下面提供了一个可行的实现,其中可以使用变通办法来实现许多所需的工作,并指出这是对真正的动态原型组合的一种折衷。
我还注意到,没有一种公开的方式要求类似super
的东西然后执行类似classes
的东西;实际上,super
或extends
可以做到。
失望了...
根据我在语言标准组的经验以及在Microsoft和其他地方作为JavaScript架构师的参与,这真令人沮丧。经过4年的讨论,他们应该像我在这里所做的那样继续讨论它或提供代码示例。不这样做就释放它的潜力很强,它可以创建遗留代码,从而使将来很难甚至无法修复(这不是标准组织所希望的)。幕后的紧张局势之一是关于静态语言与动态语言技术的长期对立观点。与其认识到两者都需要按照精心设计的“命令式”和“声明式”语义在JavaScript中正确共存。
在我看来,ES6
em>this-fn
设计应该既讨论了我在此处提出的问题,又提供了与我在此处和下文提供的代码示例相似的代码示例,以说明如何解决这些问题。
“类”在很大程度上可能是语法糖,但由于它代表了不是糖的关键部分,因此对于JS动态原型组合和更新来说,它们是静态的“易碎”,似乎不适合“声明式”与“命令式” “ JS设计语义。
解决方法: 作为一种“声明式”解决方法(仍然不是动态组合),可以使用如下功能性“模板”模型来定义MIXIN:
this-fn.proto-container.__proto__.this-selector(...)
可以将其概括为以下模式:
super(...)
使用
super.a(...)
假设,我们可以将其重写为:
super
有了我们的 mixin-template-fn polyfill 支持,我们可以模拟 ESS(ess.dev)
const Mx1 = super_type => class extends super_type {a(){super.a()} class Y extends Mx1(X) {}
的行为。让我们反思和询问 根据我们的 mixin-template-fn 构造的class Y extends X.mixin(Mx1,...) { }
class MixinBase { static mixin(...mixinTemplateFns) { const compose = (super_type,mixinTemplateFn) => { const mixin = mixinTemplateFn(super_type) const mxNameDefn = Reflect.getOwnPropertyDescriptor(mixinTemplateFn,'name') if(mxNameDefn) { Reflect.deleteProperty(mixin,'name') Reflect.defineProperty(mixin,'name',mxNameDefn) // Polyfill support for: "ess.dev" `this-fn` and "qks.st/S#" // PerspectiveType "family" (protected) selector-namespace `super` Reflect.defineProperty(mixin,'$mixin_'+mxNameDefn.value,{value: mixin,writeable: false}) } return mixin // thanks "Bergi"; catching this line got elided } // thanks "Bergi" suggesting `reduceRight` to replace `reverse().reduce` return mixinTemplateFns.reduceRight(compose,this); } }
。
JS当前不允许任何功能(const M0 = $class => class extends $class {a(){}} // only for exemplar mixin(M1,M0) const M1 = $class => class extends $class {a(){super.a();...} class X extends MixinBase {a(){...}} // <= M1 injects here as expected and supports `super` as expected => class Y extends X.mixin(M1) {...} class Z extends X.mixin(M1,M0) {...} const y = new Y y.a()
除外) 只写this-fn
。 即,它没有no-op语义,当没有mixin_
。
它需要使用class
捕获输入参数。所以,必须 对扩展变量进行硬编码,然后显式传递参数 声明为constructor
隐式地做到这一点。
我们不能通用地引用super(...)
。
super[this-selector]
如果您想玩发现游戏并走spread-arg
链,则可以使用以下内容(它与super(...)
的{{1}} polyfill修复程序一起使用)。
this-selector
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。