我尝试通过访问它作为super
的属性来修改父类的方法。我有两个问题:
super.getTaskCount
没有更新父类中引用的方法?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中,第三步是使用env.GetSuperBase()
检索'baseValue'。这里的'env'指的是最近的环境记录,它有自己的'this'绑定。环境记录是对闭包或范围进行建模的规范概念 - 它不是完全相同的东西,但现在已足够接近。
在env.GetSuperBase中,有一个对环境记录的[[HomeObject]]
的引用。这里的双括号表示与spec模型相关联存储的数据。环境记录的HomeObject与被调用的相应函数的[[HomeObject]]相同(如果存在)(它不在全局范围内)。
什么是函数的HomeObject?当一个方法在语法上创建时(在对象文字或类体中使用foo() {}
语法),该方法与创建它的对象'on'相关联 - 这就是它的'home对象'。对于类体中的方法,这意味着普通方法的原型和静态方法的构造函数。与通常完全“可移植”的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
],其严格的参考标志是严格的。“
这里有两件有趣的事情。一个是它表示'超级参考'是一种特殊的参考,另一个是......'参考'可以是一种返回类型! JavaScript没有具体的'引用',只有值,所以给出了什么?
参考确实存在作为规范概念,但它们只是规范概念。引用永远不是JavaScript中可“触摸”的具体值,而是评估其他内容的瞬态部分。要了解规范中存在这些类型的引用值的原因,请考虑以下语句:
var foo = 2;
delete foo;
在这个'undeclares'变量'foo'的表达式中,很明显右侧是作为参考而不是值2
。比较console.log(foo)
,其中,一如既往地从JS代码的POV,foo'是'2.类似地,当我们执行赋值时,bar.baz = 3
的左侧是对值baz
的属性bar
的引用,并且在bar = 3
中, LHS是对当前环境记录(范围)的绑定(变量名称)bar
的引用。
我说我要尽量避免在这里任何一个兔子洞太深,但我失败了! ...我的观点主要是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
现在是Reference
类型的Super Reference
。知道rval
是右侧的评估值,我们可以跳到步骤1.e:PutValue(lref, rval)
。
如果发生错误,PutValue开始提前退出,并且早退出lref
值(此处称为V
)不是Reference
(例如2 = 7
- ReferenceError)。在步骤4中,base
被设置为GetBase(V)
,因为这是一个超级参考,再次是原型的[[Prototype]],对应于创建该方法的类体。我们可以跳过第5步;引用是可解析的(例如,它不是未声明的变量名称)。 SuperProperty确实满足HasPropertyReference
,所以我们继续进入步骤6的子步骤.base
是一个对象,而不是原始的,所以我们跳过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)将与this
相同,SoftwareProject
是base[[Set]]()
的当前实例。那只是知道Ordinary Object [[set]]做了什么。
当我们在括号中看到“方法调用”时,它是对众所周知的内部操作的引用,其实现因对象的性质而异(但通常是相同的)。在我们的例子中,base是一个普通的对象,所以它是OrdinarySetWithOwnDescriptor。这又调用了调用this
的OrdinarySet。在这里,我们点击了步骤3.d.iv,我们的旅程结束了... ...成功的任务!?
还记得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
被传下来吗?这是作业的目标,而不是超级基础。这不是SuperProperty独有的;例如,访问者也是如此:
// 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());
那里的访问器以后代实例作为其接收器调用,超级属性就是这样。让我们对你的例子做一个小调整,看看:
super
这是一个很棒的问题 - 保持好奇心。
tl; dr:在SuperProperty中的
this
'是'super.getTaskCount = x
,但所有属性查找都是从最初定义方法的类原型的原型开始的(或构造函数的原型,如果方法是静态的)。在这个特殊的例子中,this.getTaskCount = x
可以与class Project { getTaskCount() { return 50; } } // Child class class SoftwareProject extends Project { getTaskCount() { // Let's try to modify "getTaskCount" method of parent class let getTaskCount = Project.prototype; Project.prototype.getTaskCount = function() { return 90; }; let count = super.getTaskCount() + 6; Project.prototype.getTaskCount = getTaskCount; return count; } } let p = new SoftwareProject(); console.log(p.getTaskCount());
互换。
覆盖超级方法不是好设计,但如果你真的想改变,你可以这样做
qazxswpoi