子扩展类方法调用其父版本,但仍只能看到子数据 [英] child extended class method calls its super version but that still only sees child data

查看:73
本文介绍了子扩展类方法调用其父版本,但仍只能看到子数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

B类扩展了A类.我将A称为父级,将B称为子级.两者都有构造函数. B在其构造函数内部调用super().两者都具有相同名称的方法.也许只是由于巧合或错误,两个变量都有一个"this.x"变量.这样就无法访问父级的this.x变量.这样一来,孩子与父母之间可能会发生意料之外的交流.

class B extends class A. I'll call A the parent and B the child. Both have constructors. B calls super() inside of its constructor. Both have a method with the same name. Perhaps just by coincidence or mistake both have a 'this.x' variable. There then becomes no way to access the parent's this.x variable. It then becomes a point of perhaps unintended communication between the child and parent.

   class A {
      constructor(){
        this.x = "super x!";
      }
      logx(){
        console.log(this.x);
      }
    }

    class B extends A{
      constructor(){
        super();
        this.x = "derived x.";
      }
      logx(){
        super.logx();
      }
    }

    let b = new B;
    b.logx();  // expected "super x!", but it prints "derived x".

A类可能来自库,或者是由其他人编写的.甚至可能是类A的作者来并编辑代码并添加新变量的情况,该变量然后以他甚至不知道存在的孩子作为别名.然后,子类的作者必须成为对父类所做的更改的狂热读者,以便他或她可以相应地更新自己的代码(如果确实是该作者仍在项目中). (今天正是这样的错误将我引到了这里,这是它的提炼.)

It might be the case that class A comes from a library, or was written by someone else. It might even be the case that the author of class A comes and edits the code and adds a new variable, which then aliases against a child that he doesn't even know exists. The author of the child class must then become an avid reader of changes in the parent class so he or she can update his or her own code accordingly, if indeed this author is even still on the project. (It is such a bug that has lead me here today, this is the distillation of it.)

在下面的代码中,我通过给每个变量一个与类名相同的前缀来防止此问题.然后,我得到了预期的行为.当然有更好的方法.也许其中一些私人/公共关键字会有所帮助?

In the following code I prevent this problem by giving every variable a prefix that is the same as the class name. Then I get the expected behavior. Surely there is a better way. Perhaps some of these private / public keywords would help?

      constructor(){
        this.A_x = "super x!";
      }
      logx(){
        console.log(this.A_x);
      }
    }

    class B extends A{
      constructor(){
        super();
        this.B_x = "derived x.";
      }
      logx(){
        super.logx();
      }
    }

    let b = new B;
    b.logx();  // expected "super x!", and indeed it prints "super x!"

方法调用也会发生这种情况,尽管这并不奇怪,因为a)被认为是多态性" b)通常情况下,对上游代码的接口进行更改会对下游代码产生影响.但是,程序员可能具有一些不打算在接口上使用的辅助功能,并且如果子类作者碰巧想到了相同的辅助功能名称,或者使用该名称扩展了带有功能的接口...

This also happens for method calls, though that is less surprising because a) that is considered 'polymorphism' b) it is usual that changes to the interface of upstream code has downstream code effects. However, a programmer might have some auxiliary functions not intended to be on the interface, and if a child class author happens to think of the same auxiliary function name, or extends the interface with a function by that name ...

   class A {
      constructor(){
        this.x = "super x!";
      }
      f(){
        console.log("I am a super f()!");
      }
      logx(){
        this.f(); // aliased - polymorphism behavior
        console.log(this.x);
      }
    }

   class B extends A{
      constructor(){
        super();
        this.x = "derived x.";
      }
      f(){
        console.log("I am a derived f()");
      }
      logx(){
        super.logx();
      }
    }

   let b = new B;
   b.logx();

控制台输出:

I am derived f()
derived x.

根据乔纳斯·威尔姆斯(Jonas Wilms)对正在发生的事情的解说,确实可以使用合成模式来封装父母的数据,从而防止偶然的混叠:

As per Jonas Wilms comment on his unwinding of what is going on, it is true that the composition pattern can be used to encapsulate the parent's data and thus prevent aliasing by accident:

   class A {
      constructor(){
        this.x = "super x!";
      }
      f(){
        console.log("I am a super f()!");
      }
      logx(){
        this.f();
        console.log(this.x);
      }
    }

    class B {
      constructor(){
        this.a = new A();
        this.x = "derived x.";
      }
      f(){
        console.log("I am a derived f()");
      }
      logx(){
        this.a.logx();
      }
    }

    let b = new B;
    b.logx();

它的行为与预期的一样,控制台输出:

And it behaves as expected, the console output:

    I am a super f()!
    super x!

但是,这并非没有问题.首先,instanceof运算符不起作用.其次,我们不继承 any 方法.子类的作者将不得不添加仅包含参数并将其传递给父类方法的存根.这可能会影响性能.参见 ES6等.可以定义一个包罗万象的方法吗?.

However, this is not without its problems. Firstly, the instanceof operator does not work. Secondly, we don't inherit any methods. The author of the child class will have to add stubs that just take the arguments and pass them to the parent class method. This might have performance implications. See ES6 et al. is it possible to define a catch-all method?.

..看来这个问题可以归结为:如何定义接口上的内容,什么不是?"和gee,这说明了为什么有人会喜欢这样做.

.. it seems this question boils down to, 'how do you define what is on the interface, and what isn't?' and gee, there is a demonstration of why someone might like to do this.

推荐答案

实际上,您的class层次结构等于

Actually your class hierarchy is equal to

 // a constructor is just a function
 function A() {
  this.x = "super x!";
}

A.prototype.logx = function() { console.log(this.x); };

function B() {
  A.call(this); // "this" gets passed, no new instance gets created
  this.x = "derived x";
}

B.prototype = Object.create(A.prototype); // extending a class basically lets the prototype of the class inherit the prototype of the superclass
B.prototype.constructor = B;

B.prototype.logx = function() {
  A.prototype.logx.call(this); // we can reference A#logx as it exists on the prototype
};

// using "new" basically creates a new object inheriting the prototype, then executes the constructor on it
let b = Object.create(B.prototype);
B.call(b);

因此,尽管实际上您可以引用两个logx方法(一个在A的原型上,一个在B的方法上),但只有一个实例(this)在构造期间通过,并且设置了一个对象将覆盖先前的值.因此,您是对的,没有办法使用相同的名称来拥有不同的属性.

So while there are actually two logx methods that you can reference (one on A's prototype and one on B's), there is just one instance (this) that gets passed through during construction, and setting a property of an object overrides the previous value. Therefore you are right, there is no way to have different properties with the same name.

天哪,希望不必采取任何措施,例如,如果要确保父变量保持独立,则可以采用基于其类名为每个变量赋予前缀的约定

Gosh hope one doesn't need to do something such as adopt a convention of giving every variable a prefix based on its class name if one wants to assure that parent variables remain independent

我真的建议您使用Typescript来关注结构(有一个privatereadonly属性修饰符).在JS中,您可以使用符号来模仿私有属性:¹

I really recommend using Typescript to keep an eye on the structure (there is a private and readonly property modifier). In JS you could use Symbols to mimic private properties:¹

 class A {
   constructor() {
     this[A.x] = "stuff";
   }
 }

 A.x = Symbol();

 class B extends A {
   constructor() {
     this[B.x] = "other stuff";
   }
 }

 B.x = Symbol();

 console.log(new B()[A.x]);

(确保您可以将Symbols保留在任何类型的变量中,而无需使其成为类的一部分).

(for sure you can keep the Symbols in any kind of variable, no need to make it part of the class).

或者您只是放弃继承的东西,并用A 组成 B:

Or you just give up the inheritance thing, and compose B with A:

 class B {
   constructor() {
     this.a = new A();
     this.x = "new x";
   }
 }

 (new B).x
 (new B).a.x

方法调用也会发生这种情况吗?

Will this also happen for method calls?

是的,因为B实例的继承链是:

Yes, as the inheritance chain for an instance of B is:

 b -> B.prototype -> A.prototype

该方法将首先在b中查找,然后在B中查找,最后在A中查找,因此,如果在A和B中都有名称为"logx"的方法,则B中的一个将被采取.您也可以这样做:

The method will be looked up in b first, then in B and finally in A, so if there is a method with the name "logx" in both A and B, the one of B will be taken. You could also do:

 b.logx = function() { console.log("I'm first!");

那么,如果要父代f()的话,编写父代代码时会做什么?

so, what does one do when writing parent code, if one wants the parent f()?

您可以直接在原型上调用它:

You could directly call it on the prototype:

 A.prototype.logx.call(b /*...arguments*/);

从方法中的

可以采用this而不是具体实例(在此示例中为b).如果您不想采用特定的实现,而是使用 super 类之一,请像以前一样使用super.logx().

from within a method you can take this instead of a concrete instance (b in this example). If you don't want to to take the specific implementation, but the one of the superclass, use super.logx() just as you did.

¹老实说:只要您正确命名属性,我就不会遇到任何问题,名称实际上很少会冲突.

¹ To be honest: I never had any problems with that, as long as you name your properties properly, names will really rarely clash.

这篇关于子扩展类方法调用其父版本,但仍只能看到子数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆