Вызовите родительскую функцию, которая переопределяется дочерним элементом во время цепочки конструкторов в JavaScript (ES6)

Я столкнулся с проблемой ниже с JavaScript (ES6)

class A{
  constructor(){
    this.foo();
  }
  foo(){
    console.log("foo in A is called");
  }
}

class B extends A{
  constructor(){
    super();
    this.foo();
  }
  foo(){
    console.log("foo in B is called");
  }
}

Я ожидаю, что

foo in A is called
foo in B is called

Но на самом деле это

foo in B is called
foo in B is called

Я знаю, что могу решить эту проблему, просто добавив super.foo() в функцию foo класса B.

class B extends A{
  constructor(){
    super();
    this.foo();
  }
  foo(){
    super.foo() // add this line
    console.log("foo in B is called");
  }
}

Но представьте сценарий, похожий на этот:

Ребенок должен переопределить родительскую функцию, чтобы выполнить некоторые дополнительные действия и предотвратить доступ извне, чтобы получить доступ к исходному.

class B extends A{
  constructor(){
    super();
    this.initBar();
  }
  foo(){
    super.foo();
    this.bar.run(); //undefined
    console.log("foo in B is called");
  }
  initBar(){
    this.bar.run = function(){
      console.log("bar is running");
    };
  }
}

Кажется, что this по-прежнему указывает на дочерний B при построении в родительском A. Вот почему я не могу связаться с foo родителя A.

Как заставить this вызывать функцию родительской версии, которая переопределяется дочерним элементом во время цепочки конструкторов?

Или есть лучшее решение, когда дело доходит до такого сценария?

Изменить

Итак, после прочтения ответов главный вопрос становится --

Не рекомендуется ли помещать initialize helpers или setter functions в конструктор в JavaScript, поскольку у детей есть возможность переопределить их?

Чтобы прояснить ситуацию более четко: (извините за мой предыдущий плохой пример: ()

class A{
  constructor(name){
    this.setName(name);
  }
  setName(name){
    this._name = name;
  }
}

class B extends A{
  constructor(name){
    super(name);
    this._div = document.createElementById("div");
  }
  setName(name){
    super.setName(name);
    this._div.appendChild(document.createTextNode(name));
  }
}

new B("foo")

this._div будет undefined.

Это плохая идея, так как ребенок сможет переопределить функцию?

class A{
  constructor(name){
    this.setName(name); // Is it bad?
  }
  ...
}

Так что я не должен использовать initialize helpers или setter functions в конструкторе, как в Java, C++...?

Должен ли я вручную вызывать подобные вещи new A().init(), чтобы помочь мне инициализировать вещи?


person andrew    schedule 16.09.2015    source источник
comment
Как заставить this вызывать функцию родительской версии - super - единственный способ.   -  person thefourtheye    schedule 16.09.2015
comment
Почему конструктор A вообще вызывает this.foo()? Он не должен, он должен только инициализировать экземпляр.   -  person Bergi    schedule 16.09.2015
comment
Предназначен ли дочерний элемент foo для переопределения родительской реализации в общем случае, или вы больше спрашиваете, как сделать foo некоторым частным помощником?   -  person loganfsmyth    schedule 16.09.2015
comment
Синтаксис класса ES6 по-прежнему генерирует прототип, как мы привыкли передавать код. Итак, когда вы определяете foo в классе B, оно по-прежнему переопределяет определение foo в прототипе, и это this.foo() независимо от того, где вы его вызываете, будет ссылаться на определение класса B. Как вы, кажется, уже знаете, если вы хотите вызвать версию foo класса A, вы должны сделать это вручную.   -  person jfriend00    schedule 16.09.2015
comment
К вашему сведению, вы, кажется, думаете, что есть два объекта: один для ребенка и один для родителя. Это не так. this не указывает на дочерний элемент B. this указывает на весь объект, в котором и A, и B имеют свойства экземпляра и методы прототипа. Существует только один объект, в котором свойства и методы вносят вклад как A, так и B. Вот почему есть только один this и почему this.foo указывает на самое последнее переопределение.   -  person jfriend00    schedule 16.09.2015
comment
Вероятно, стоит отметить, что вы должны получить точно такой же результат с эквивалентным кодом на Java, Python и C++.   -  person cosmicFluke    schedule 09.06.2016


Ответы (1)


Похоже, вы ошибаетесь, считая, что существует два объекта A и B, когда вы находитесь в конструкторе производного класса B. Это совсем не так. Есть один и только один объект. И A, и B вносят свойства и методы в этот объект. Значение this в конструкторе для B будет таким же, как и в конструкторе для A при создании объекта класса B.

Синтаксис класса ES6 — это просто сахар по сравнению с методом ES5, использующим прототипы для типов объектов, и, по сути, прототип все еще используется скрыто. Таким образом, когда вы определяете метод foo в производном классе, как вы это делаете в классе B, он по-прежнему назначается прототипу, и это назначение переопределяет любой метод с тем же именем, который может уже существовать в прототипе, полученном из родительского определения. . Вот почему this.foo() относится к версии foo класса B. Если вы хотите достичь версии foo класса A, вам придется вручную указать это, используя super, как вы, кажется, уже знаете.

Что касается ваших конкретных вопросов:

Кажется, что это по-прежнему указывает на дочерний элемент B при построении в родительском элементе A. Вот почему я не могу получить доступ к родительскому элементу A foo.

ребенок B и родитель A не являются отдельными объектами. Существует один объект, на который ссылаются и родительский A, и дочерний B. родительские методы или конструкторы A и дочерние B будут видеть точно такое же значение this.

Как мне заставить это вызывать функцию родительской версии, которая переопределяется дочерним элементом во время цепочки конструкторов?

Вы используете super для прямой ссылки на родительские методы, поскольку вы, кажется, уже знаете.

Или есть лучшее решение, когда дело доходит до такого сценария?

super это решение.


К вашему сведению, это довольно хорошее обсуждение классов ES6, включая то, как работает super: Классы в ECMAScript 6 (окончательная семантика). Раздел 4.4 кажется особенно актуальным для вашего вопроса/понимания.

person jfriend00    schedule 16.09.2015
comment
Я думаю, что лучше не использовать сеттеры в родительском конструкторе, например this.setName(name); и будет лучше использовать this.name = name; например, когда вы используете super(), вы не будете использовать функции переопределения, о которых вы не знаете. - person llioor; 07.02.2017