javascript - 为什么在 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
它的正确使用和限制。
解决方案
表面上,super
似乎很像this
。但这是一个很大的不同,细节并不完全直观。关于其真实性质的第一个提示是关键字super
本身浮动在语法上是无效的。
console.log(this); // works; `this` refers to a value
console.log(super); // throws a SyntaxError
相反,SuperCall — super()
— 是某些构造函数中可用的特殊语法,而 SuperProperty —super.foo
或super[foo]
— 是方法中可用的特殊语法。在这两种情况下,表达式都不能进一步简化super
为独立于其右手边的部分。
在我们了解当 SuperProperty 位于分配的左侧时会发生什么之前,我们需要查看评估 SuperProperty 本身的真正作用。
在ECMA-262, § 12.3.5中,描述的前两种情况对应于 SuperProperty 生产并且非常相似。您会看到,这两种情况下的算法都以检索当前this
值开始,并以继续进行MakeSuperPropertyReference操作结束,我们接下来应该看一下。
(我将省略某些步骤的作用,因为如果我们遍历所有内容,我们会整天在这里;相反,我想提请注意与您的问题特别相关的部分。)
在 MakeSuperPropertyReference 中,第三步是检索 'baseValue' env.GetSuperBase()
。这里的“env”是指最近的环境记录,它有自己的“this”绑定。环境记录是对闭包或作用域进行建模的规范概念——它并不完全相同,但现在可以这么说。
在环境中。GetSuperBase,有[[HomeObject]]
对环境记录的引用。此处的双括号表示与规范模型关联存储的数据。环境记录的 HomeObject 与被调用的相应函数的 [[HomeObject]] 相同,如果存在的话(它不会在全局范围内)。
什么是函数的 HomeObject?当一个方法在语法上创建时(使用foo() {}
对象文字或类主体中的语法),该方法与创建它的“对象”相关联——这是它的“主对象”。对于类体中的方法,这意味着普通方法的原型和静态方法的构造函数。与this
通常完全“可移植”的 不同,方法的 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
,我们将从最后一步获得下一个线索:
“返回一个 Reference 类型的值,它是一个超级引用,其基值组件是 bv [ed. 基值],其引用的名称组件是propertyKey,其thisValue 组件是actualThis [ed. 当前的
this
],其严格引用标志是严格的。”</p>
这里有两件有趣的事情。一个是它表明'Super Reference'是一种特殊的引用,另一个是......'Reference'可以是一个返回类型!JavaScript 没有具体化的“引用”,只有值,那又是什么呢?
引用确实作为规范概念存在,但它们只是规范概念。引用永远不是 JavaScript 中“可触摸”的具体值,而是评估其他内容的临时部分。要了解为什么规范中存在这些类型的参考值,请考虑以下语句:
var foo = 2;
delete foo;
在“取消声明”变量“foo”的删除表达式中,很明显右侧 ( foo
) 是作为对绑定本身的引用而不是作为值2
。比较console.log(foo)
,其中,正如从 JS 代码中所观察到的, foo 'is' 2. 同样,当我们执行赋值时,左侧bar.baz = 3
是对 value属性的引用,而在 中,LHS 是对当前环境记录(范围)的绑定(变量名)。baz
bar
bar = 3
bar
我说我会尽量避免在这里的任何一个兔子洞上走得太深,但我失败了!...我的观点主要是 SuperReference 不是最终的返回值——它永远不能被 ES 代码直接观察到。
如果在 JS 中建模,我们的超级参考看起来像这样:
const superRef = {
base: Object.getPrototypeOf(SoftwareProject.prototype),
referencedName: 'getTaskCount',
thisValue: p
};
那么,我们可以分配给它吗?让我们看看在评估正常作业时会发生什么以找出答案。
在此操作中,我们满足第一个条件(SuperProperty 不是 ObjectLiteral 或 ArrayLiteral),因此我们继续执行以下子步骤。SuperProperty 被评估,所以lref
现在是 aReference
类型Super Reference
。知道这rval
是右侧的评估值,我们可以跳到步骤 1.e.: PutValue(lref, rval)
。
如果发生错误,PutValuelref
首先会提前退出,如果值(此处称为V
)不是 a Reference
(例如2 = 7
,ReferenceError),也会提前退出。在第 4 步中,base
设置为GetBase(V)
,因为这是一个Super Reference,它再次是原型的 [[Prototype]] 对应于在其中创建方法的类主体。我们可以跳过第 5 步;引用是可解析的(例如,它不是未声明的变量名)。SuperProperty 确实满足HasPropertyReference
,所以我们继续进入步骤 6 的子步骤。它base
是一个对象,而不是一个基元,所以我们跳过 6.a。然后它发生了!6.b——作业。
b. Let succeeded be ? base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
好吧,无论如何。旅程并不完整。
我们现在可以super.getTaskCount = function() {}
在您的示例中翻译它。基地将是Project.prototype
。GetReferenceName(V) 将计算为字符串“getTaskCount”。W 将评估右侧的函数。GetThisValue(V) 将与this
的当前实例相同SoftwareProject
。那只剩下知道做什么了base[[Set]]()
。
当我们在这样的括号中看到“方法调用”时,它是对众所周知的内部操作的引用,其实现取决于对象的性质(但通常是相同的)。在我们的例子中,base 是一个普通对象,所以它是普通对象 [[set]]。这反过来又调用了 OrdinarySet ,后者调用了OrdinarySetWithOwnDescriptor。在这里,我们已经完成了步骤 3.d.iv,我们的旅程结束了……以……成功的任务!?
还记得this
被传下来吗?那是任务的目标,而不是超级基地。不过,这并不是 SuperProperty 独有的;例如,访问器也是如此:
const foo = {
set bar(value) {
console.log(this, value);
}
};
const descendent = Object.create(foo);
descendent.baz = 7;
descendent.bar = 8;
// console logs { baz: 7 }, 8
那里的访问器以后代实例作为其接收者被调用,超级属性就是这样。让我们对您的示例进行一些小调整,看看:
// Parent Class
class Project {
getTaskCount() {
return 50;
}
}
// Child class
class SoftwareProject extends Project {
getTaskCount() {
super.getTaskCount = function() {
return 90;
};
return this.getTaskCount() + 6;
}
}
let p = new SoftwareProject();
console.log(p.getTaskCount());
// 96 — because we actually assigned the new function on `this`
这是一个奇妙的问题——保持好奇。
tl; dr:
super
在 SuperProperty 'is'this
中,但所有属性查找都从最初定义方法的类的原型的原型开始(或构造函数的原型,如果方法是静态的)。但是赋值不是查找一个值,它是设置一个值,在这个特定的例子中,super.getTaskCount = x
它可以与this.getTaskCount = x
.
推荐阅读
- php - 如何获取/打印实体引用的路径或 URL 到我的树枝模板(Drupal 8)?
- kubernetes - kubernetes envFrom:如何加载位于 pod 文件系统文件中的变量
- sql - 当要更新的每一行都是不同的值时如何更新现有表
- android - 将项目导入Android Studio时如何使自动gradle构建静音?
- android - 为什么 VTS 失败并出现不准确的 adb 错误?
- react-native - 在标签栏中显示组件 react-native-router-flux
- firebase - 有什么方法可以在一个仪表板中查看 iOS 和 Android 的分析数据?
- linux - 如何在 linux 中访问 IIO 设备驱动程序
- javascript - 使用分页/块编号将字符串拆分为字符限制的块
- javascript - Javascript aws-sdk | 如何将视频发送到 kinesis 流