为什么在JavaScript中修改super.method()会失败?
作者:互联网
我尝试通过将其作为super的属性来访问它来修改父类的方法.我有两个问题:
>为什么修改super.getTaskCount没有更新父类中引用的方法?
>为什么JavaScript在修改super.getTaskCount时没有出错?代码执行过程中到底发生了什么?
我们来看看这个例子:
// Parent Class
class Project {
getTaskCount() {
return 50;
}
}
// Child class
class SoftwareProject extends Project {
getTaskCount() {
// Let's try to modify "getTaskCount" method of parent class
super.getTaskCount = function() {
return 90;
};
return super.getTaskCount() + 6;
}
}
let p = new SoftwareProject();
console.log(p.getTaskCount()); // prints 56. Why not 96?
// Why did super.getTaskCount method remain unchanged?
PS:我知道我们可以使用getter和setter来处理这种情况,但我知道
试图了解更多有关超级和正确使用的信息.限制.
解决方法:
从表面上看,超级看起来很像这样.但这是一个很好的不同,细节并不完全直观.关于其真实性质的第一个提示是关键字super floating本身在语法上无效.
console.log(this); // works; `this` refers to a value
console.log(super); // throws a SyntaxError
相反,SuperCall – super() – 是一些构造函数中可用的特殊语法,而SuperProperty – super.foo或super [foo] – 是方法中可用的特殊语法.在任何情况下,表达式都不能进一步减少到与其右手侧无关的超级部分.
在我们可以了解当SuperProperty是作业的左侧时会发生什么,我们需要看看评估SuperProperty本身的真正作用.
在ECMA-262, § 12.3.5中,所描述的前两个案例对应于SuperProperty生产并且非常相似.你会看到两种情况下的算法都是从检索当前的这个值开始,然后继续执行MakeSuperPropertyReference操作,我们应该看一下下一步.
(如果我们一整天都在这里,我会完全不知道一些步骤,因为我们整天都在这里;相反,我想提请注意那些与你的问题有关的特别感兴趣的部分.)
在MakeSuperPropertyReference中,第三步是使用env.GetSuperBase()检索’baseValue’.这里的’env’是指最近的环境记录
它有自己的’this’绑定.环境记录是对闭包或范围进行建模的规范概念 – 它不是完全相同的东西,但现在已足够接近.
在env.GetSuperBase中,有一个对环境记录的[[HomeObject]]的引用.这里的双括号表示与spec模型相关联存储的数据.环境记录的HomeObject与被调用的相应函数的[[HomeObject]]相同(如果存在)(它不在全局范围内).
什么是函数的HomeObject?当一个方法在语法上创建时(在对象文字或类体中使用foo(){}语法),该方法与创建它的对象’on’相关联 – 这就是它的’home对象’.对于类体中的方法,这意味着普通方法的原型和静态方法的构造函数.与此不同,通常完全是“可移植的”,方法的HomeObject永久固定为特定值.
HomeObject本身不是“超级对象”.相反,它是对象的固定引用,从中可以派生出“超级对象”(基础).实际的“超级对象”或基础是HomeObject的当前[[Prototype]].因此,即使[[HomeObject]]是静态的,super指的对象也可能不是:
class Foo { qux() { return 0; } }
class Baz { qux() { return 1; } }
class Bar extends Foo { qux() { return super.qux(); } }
console.log(new Bar().qux());
// 0
console.log(Bar.prototype.qux.call({}));
// also 0! the [[HomeObject]] is still Bar.prototype
// However ...
Object.setPrototypeOf(Bar.prototype, Baz.prototype);
console.log(new Bar().qux());
// 1 — Bar.prototype[[Prototype]] changed, so GetSuperBase resolved a different base
所以现在我们对’super.getTaskCount’中的’super’有了更多的了解,但是仍然不清楚为什么分配它失败了.如果我们现在回顾一下MakeSuperPropertyReference,我们将从最后一步获得下一个线索:
“Return a value of type Reference that is a Super Reference whose base value
component is bv [ed. the base value], whose referenced name component is
propertyKey, whose thisValue component is actualThis [ed. the currentthis
],
and whose strict reference flag is strict.”
这里有两件有趣的事情.一个是它表示’超级参考’是一种特殊的参考,另一个是……’参考’可以是一种返回类型! JavaScript没有具体的’引用’,只有值,所以给出了什么?
参考确实存在作为规范概念,但它们只是规范概念.引用永远不是JavaScript中可“触摸”的具体值,而是评估其他内容的瞬态部分.要了解规范中存在这些类型的引用值的原因,请考虑以下语句:
var foo = 2;
delete foo;
在这个’undeclares’变量’foo’的表达式中,很明显右侧是作为参考而不是值2.比较console.log(foo),其中,一如既往地来自JS的POV代码,foo’是’2.类似地,当我们执行赋值时,bar.baz = 3的左侧是对值栏的属性baz的引用,而在bar = 3中,LHS是对当前环境记录(范围)的绑定(变量名称)栏.
我说我要尽量避免在这里任何一个兔子洞太深,但我失败了! …我的观点主要是SuperReference不是最终的返回值 – 它永远不会被ES代码直接观察到.
如果在JS中建模,我们的超级参考将看起来像这样:
const superRef = {
base: Object.getPrototypeOf(SoftwareProject.prototype),
referencedName: 'getTaskCount',
thisValue: p
};
那么,我们可以分配给它吗?让我们看看what happens when evaluating a normal assignment了解一下.
在此操作中,我们满足第一个条件(SuperProperty不是ObjectLiteral或ArrayLiteral),因此我们继续执行后面的子步骤. SuperProperty被评估,因此lref现在是超级参考类型的参考.知道rval是右侧的评估值,我们可以跳到步骤1.e:PutValue(lref,rval).
如果发生错误,PutValue开始提前退出,并且提前退出lref值(此处称为V)不是Reference(例如2 = 7 – ReferenceError).在步骤4中,base被设置为GetBase(V),因为这是一个Super Reference,它再一次是原型的[[Prototype]],对应于创建该方法的类体.我们可以跳过第5步;引用是可解析的(例如,它不是未声明的变量名称). SuperProperty确实满足HasPropertyReference,所以我们继续进入步骤6的子步骤.基础是一个对象,而不是一个基元,所以我们跳过6.a.然后它发生了! 6.b – 作业.
b. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
好吧,无论如何,sorta.旅程尚未完成.
我们现在可以在你的例子中为super.getTaskCount = function(){}翻译它.基数将是Project.prototype. GetReferenceName(V)将计算为字符串“getTaskCount”. W将评估右侧的功能. GetThisValue(V)将与此相同,即SoftwareProject的当前实例.这只是知道什么基[[Set]]()的作用.
当我们在括号中看到“方法调用”时,它是对众所周知的内部操作的引用,其实现因对象的性质而异(但通常是相同的).在我们的例子中,base是一个普通的对象,所以它是Ordinary Object [[set]].这反过来调用了拨打OrdinarySetWithOwnDescriptor的OrdinarySet.在这里,我们点击了步骤3.d.iv,我们的旅程结束了……成功的任务! ?
请记住,这是传下来的?这是作业的目标,而不是超级基础.这不是SuperProperty独有的;例如,访问者也是如此:
const foo = {
set bar(value) {
console.log(this, value);
}
};
const descendent = Object.create(foo);
descendent.baz = 7;
descendent.bar = 8;
// console logs { bar: 7 }, 8
那里的访问器以后代实例作为其接收器调用,超级属性就是这样.让我们对你的例子做一个小调整,看看:
// Parent Class
class Project {
getTaskCount() {
return 50;
}
}
// Child class
class SoftwareProject extends Project {
getTaskCount() {
// Let's try to modify "getTaskCount" method of parent class
super.getTaskCount = function() {
return 90;
};
return this.getTaskCount() + 6;
}
}
let p = new SoftwareProject();
console.log(p.getTaskCount());
这是一个很棒的问题 – 保持好奇心.
tl;dr:
super
in a SuperProperty ‘is’this
, but with all property lookups starting from the prototype of the prototype of the class on which the method was originally defined (or the prototype of the constructor, if the method is static). In this particular example,super.getTaskCount = x
is interchangeable withthis.getTaskCount = x
.
标签:javascript,ecmascript-6,es6-class 来源: https://codeday.me/bug/20190823/1696879.html