我尝试通过将其作为super的属性来访问来修改父类的方法。这里有两个问题:

  • 为什么修改super.getTaskCount不会更新父类中引用的方法?
  • 为什么修改super.getTaskCount时JavaScript没有给出任何错误?代码执行过程中到底发生了什么?

  • 让我们来看一个例子:

    // 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。但这有很大的不同,细节也不是很直观。关于它的真实性质的第一个提示是,单独 float 的关键字super在语法上无效。

    console.log(this);  // works; `this` refers to a value
    console.log(super); // throws a SyntaxError
    

    相反,SuperCall(super())是某些构造函数中可用的特殊语法,而SuperProperty(super.foosuper[foo])是方法中可用的特殊语法。在任何一种情况下,表达式都不能进一步简化为独立于其右侧的super部分。

    在我们了解SuperProperty在任务的左侧时会发生什么之前,我们需要研究一下SuperProperty本身的评估工作。

    ECMA-262, § 12.3.5中,描述的前两种情况对应于SuperProperty产品,并且非常相似。您会看到,两种情况下的算法都首先检索当前的this值,然后继续进行MakeSuperPropertyReference操作,我们接下来将介绍该操作。

    (我将阐明一些步骤,因为如果我们一整天都待在这里,我们将整日待在这里;相反,我想提请注意与您的问题特别相关的部分。)

    在MakeSuperPropertyReference中,第三步是使用env.GetSuperBase()检索“baseValue”。这里的“env”是指最近的环境记录
    具有自己的“this”绑定(bind)环境记录是一种规范概念,用于对闭包或范围进行建模-并非完全相同,但现在已经足够接近了。

    在环境中GetSuperBase,是对环境记录的[[HomeObject]]的引用。此处的双括号表示与规格模型关联存储的数据。环境记录的HomeObject与所调用的相应函数的[[HomeObject]]相同(如果存在)(不在全局范围内)。

    什么是函数的HomeObject?通过语法创建方法(在对象文字或类主体中使用foo() {}语法)时,该方法与在其上创建的对象“上”相关联-即它的“主对象”。对于类主体中的方法,这意味着常规方法的原型(prototype)和静态方法的构造函数。与通常完全“可移植”的this不同,方法的HomeObject永久固定为特定值。

    HomeObject本身不是“ super 对象”。相反,它是对对象的固定引用,从中可以得出“ super 对象”(基本)。实际的“ super 对象”或基础就是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,我们将从最后一步获得下一个线索:



    这里有两个有趣的事情。一个是表示“ super 引用”是一种特殊的引用,另一个是……“引用”完全可以是返回类型! JavaScript没有统一化的“引用”,只有值,那么有什么用呢?

    引用确实作为规范概念存在,但它们只是规范概念。引用绝不是来自JavaScript的可触摸的固定值,而是评估其他内容的过渡部分。要了解为什么规范中存在这些引用值,请考虑以下语句:

    var foo = 2;
    delete foo;
    

    在删除表达式中,“取消声明”了变量“foo”,很明显,右侧(foo)充当对绑定(bind)本身的引用,而不是2值。比较console.log(foo),其中,与从JS代码中观察到的一样,foo'是'2。类似地,当我们执行赋值时,bar.baz = 3的左侧是对值bazbar属性的引用,在bar = 3中是LHS是对当前环境记录(作用域)的绑定(bind)(变量名)bar的引用。

    我说过我要尽量避免在这里的任何一个兔子洞中走得太深,但是我失败了! ...我的观点主要是SuperReference不是最终的返回值-ES代码永远无法直接观察到它。

    如果使用JS建模,我们的 super 引用将看起来像这样:

    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),因为这是一个 super 引用,它再次是与在其中创建方法的类主体相对应的原型(prototype)的[[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是一个普通对象,所以它是Ordinary Object [[set]]。依次调用OrdinarySet,后者调用OrdinarySetWithOwnDescriptor。在这里,我们将执行步骤3.d.iv,我们的旅程将以...成功的分配而结束!

    还记得this被传下来吗?那是分配的目标,而不是 super 基础。不过,这并非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
    

    那里的访问者以后代实例作为接收者被调用,并且 super 属性就是这样。让我们对您的示例进行一些小的调整,看看:

    // 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`


    这是一个奇妙的问题-保持好奇心。

    09-17 20:18