Наследование JS: вызов родительской функции из дочерней функции
Должно быть что-то, что я не понимаю о объектной модели JS.
Из этих ресурсов:
Я собрал то, что я думаю, или думал, был точным ментальным представлением объектной модели. Вот он:
Все объекты имеют свойство, которое документы называются [[Prototype]]
. [[Prototype]]
можно рассматривать как ссылку на родительский объект. Точнее:
Ссылка на объект-прототип [родителя] копируется в internal [[Prototype]]
свойство нового экземпляра. (источник 1)
Вы можете получить доступ к свойству [[Prototype]]
для ребенка с помощью Object.getPrototypeOf(child)
. Возвращаемое здесь значение будет ссылкой на родительский прототип (а не его внутреннее свойство [[Prototype]]
, а его фактический прототип)
obj.prototype
отличается от внутреннего свойства объекта [[Prototype]]
. Он действует как чертежи, используемые для создания экземпляров этого точного объекта, а его свойство [[Prototype]]
указывает на чертежи, используемые для создания экземпляров его родителя.
-
Parent.prototype === Object.getPrototypeOf(child); //true
Разработать:
-
Если вы добавите функцию в child.prototype
, функция будет доступна для child
и любого из них.
-
Если вы добавите функцию в parent.prototype
, что эквивалентно добавлению функции к Object.getPrototypeOf(child)
, тогда функция будет доступна для parent
и всех ее дочерних элементов, которая включает child
и все его siblings
.
Вы можете использовать Object.create()
для создания нового объекта с любым требуемым свойством [[Protoype]]
. Таким образом, вы можете использовать его как способ реализации наследования. См. источник 2 для примера.
Имея это в виду, я хотел получить рабочий пример моего собственного. Мой план состоял в том, чтобы создать родительский "класс", а затем создать дочерний "класс", который унаследовал от него.
Я хотел, чтобы дочерний класс реализовал метод, который перегружал метод из родителя. Предостережение заключается в том, что я хочу, чтобы дочерняя версия метода вызывала родительскую версию метода, а затем делала некоторые дополнительные материалы.
Вот что я придумал, см. ниже связанные с этим проблемы:
var Parent = function() {};
Parent.prototype.myF = function() {
console.log('derp');
};
function Child() {
Parent.call(this);
};
//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);
//need to explicitly set the constructor
Child.prototype.constructor = Child;
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
childInstance = new Child();
childInstance.myF();
Ответы
Ответ 1
Отличный вопрос, потребовалось немного тестирования и исследования, чтобы найти его.
Определение странного поведения
Я немного изменил код, чтобы узнать, какая функция вызывается, когда:
var Parent = function() {};
Parent.prototype.myF = function() {
console.log('derp');
};
function Child() {
Parent.call(this);
this.name = 'Test'; // this is a new test property
};
//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);
//need to explicitly set the constructor
Child.prototype.constructor = Child;
Child.prototype.myF = function() {
console.log(this); // here I want to find out the context, because you use it in the next line
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
childInstance = new Child();
childInstance.myF();
Вы можете проверить JSFiddle и попробовать сами: http://jsfiddle.net/Lpxq78bs/
Ключевая строка в вашем коде:
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF(); // this one
console.log("did I derp??");
};
После выполнения console.log(this);
, чтобы узнать, к чему относится this
, я увидел, что он изменяется между первым и вторым выходом did I derp??
.
Я получил следующий вывод:
Object { name: "Test" }
Object { constructor: Child(), myF: window.onload/Child.prototype.myF() }
"derp"
"did I derp??"
"did I derp??"
Интерпретация вывода
Так как я добавил свойство name для конструктора Child
, это было бы только вокруг, если бы я рассматривал экземпляр Child
, а не его .prototype
.
Итак, первая строка Output означает, что текущий this
контекст действительно является childInstance
. Но второй не является ни childInstance
, ни Parent.prototype
:
- Вызов (
myF
of childInstance
): this
относится к childInstance
. Object.getPrototypeOf(this).myF();
затем ищет [[Prototype]]
childInstance
, , который является Child.prototype
, а не Parent.prototype
.
Выход: "я derp??"
-
Вызов (myF
of Child.prototype
): this
относится к Child.prototype
, который является свойством childInstances
[[Prototype]]. Таким образом, второй вызов Object.getPrototypeOf(this).myF();
, наконец, возвращает Parent.prototype
(вид). Выход: "я дернул?"
-
Вызов (myF
экземпляра Parent.prototype
, созданный Object.create
): Наконец, вызывается myF
родителя. Выход: 'derp'
Так как ваш console.log("did I derp??")
появляется после вызова функции myF
, выход находится в обратном порядке. На следующем рисунке показано, как проходит код:
![enter image description here]()
Итак, ваше предположение о том, что означает Object.getPrototypeOf(this).myF();
, было неправильным.
Решение в ES5
By @LukeP:
https://jsfiddle.net/Lpxq78bs/28/
Альтернативное решение в ES6
Чтобы избежать этой путаницы, и поскольку вы работаете с классическим шаблоном наследования, вы можете взглянуть на ES6 Classes. Ниже приведен пример того, что вы пытаетесь сделать:
class Parent {
constructor() {
}
myF() {
console.log('derp');
}
}
class Child extends Parent {
constructor() {
super();
}
myF() {
super.myF();
console.log("did I derp??");
}
}
var childInstance = new Child();
childInstance.myF();
Надеюсь, это поможет понять, что происходит.
Ответ 2
Ваш код работает как ожидалось, вывод, который вы получаете, связан с Object.getPrototypeOf
и может быть объяснен
Шаг 1: когда вы cal childInstance.myF();
, тогда он переходит к ниже кода, где this
относится к самому childInstance
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
затем Object.getPrototypeOf
возвращает childInstance.[[Prototype]]
, который равен Child.prototype
, и снова вызывает метод myF
(оставляя вторую строчку для печати позже после выполнения метода), но в следующий раз this
будет ссылаться на childInstance.[[Prototype]]
.
Шаг 2. В этом вызове this
отображается объект childInstance.[[Prototype]]
(или Child.prototype
)
Child.prototype.myF = function() {
Object.getPrototypeOf(this).myF();
console.log("did I derp??");
};
Теперь Object.getPrototypeOf
возвращает childInstance.[[Prototype]].[[Prototype]]
(который является Child.prototype.[[Prototype]]
), который является Parent.prototype
и снова вызывает метод myF, но на этот раз метод myF напечатает derp, потому что метод myF Parent.prototype
вызывается. После этого вторая строка будет печатать сделана я derp.
Шаг 3. Затем функция возвращается к шагу 1 для выполнения второй строки, которая снова печатает сделал я derp
Ответ 3
Если вам нужно работать с наследованием в ES5, я создал оболочку, которая упрощает расширение и вызывает родительские методы. Никаких фреймворков не требуется.
скрипт для рабочего кода здесь: https://jsfiddle.net/teche/wcrwLmrk/5/
Вот как работает код:
/* create a parent constructor function */
var ParentClass = function()
{
};
/* extend the base class to the new parent class and pass
an object with the new properties and methods */
Class.extend(
{
// reset the constructor to the correct constructor
constructor: ParentClass,
callName: function(arg)
{
console.log('parent ' + arg);
}
});
/* create a child constructor function */
var ChildClass = function()
{
};
/* extend the parent class to the new child class and pass
an object with the new properties and methods */
ParentClass.extend(
{
// reset the constructor to the correct constructor
constructor: ChildClass,
callName: function(arg)
{
// child method code
console.log('child ' + arg);
/* call parent method by passing the method name and any params */
this.super('callName', arg);
}
});
Теперь мы можем создать новый дочерний класс, который будет
вызовите метод суперкласса:
var child = new ChildClass();
child.callName('name');
Вот базовый класс, который должен быть включен в проект:
var Class = function()
{
};
Class.prototype =
{
constructor: Class,
/* this is an alias for callParent. this will
allow child classes to call super methods.
@param (string) method name
@param [(mixed)] addasmany args as the super
method needs
@return (mixed) the super method return value */
super: function()
{
var args = arguments;
return this.callParent.apply(this, args);
},
/* this will allow child classes to call super
methods.
@param (string) method name
@param [(mixed)] addasmany args as the super
method needs
@return (mixed) the super method return value */
callParent: function()
{
var parent = this.parent;
if(parent)
{
var args = [].slice.call(arguments),
// this will remove the first arg as the method
method = args.shift();
if(method)
{
var func = parent[method];
if(typeof func === 'function')
{
return func.apply(this, args);
}
}
}
return false;
}
};
/* this will return a new object and extend it if an object it supplied.
@param [(object)] object = the extending object
@return (object) this will return a new object with the
inherited object */
var createObject = function(object)
{
if(!Object.create)
{
var obj = function(){};
obj.prototype = object;
return new obj();
}
else
{
return Object.create(object);
}
};
/* this will extend an object and return the extedned
object or false.
@param (object) sourceObj = the original object
@param (object) targetObj = the target object */
var extendObject = function(sourceObj, targetObj)
{
if(typeof sourceObj !== 'undefined' && typeof targetObj !== 'undefined')
{
for(var property in sourceObj)
{
if(sourceObj.hasOwnProperty(property) && typeof targetObj[property] === 'undefined')
{
targetObj[property] = sourceObj[property];
}
}
return targetObj;
}
return false;
};
var extendClass = function(sourceClass, targetClass)
{
/* if we are using a class constructor function
we want to get the class prototype object */
var source = (typeof sourceClass === 'function')? sourceClass.prototype : sourceClass,
target = (typeof targetClass === 'function')? targetClass.prototype : targetClass;
if(typeof source === 'object' && typeof target === 'object')
{
/* we want to create a new object and add the source
prototype to the new object */
var obj = createObject(source);
/* we need to add any settings from the source that
are not on the prototype */
extendObject(source, obj);
/* we want to add any additional properties from the
target class to the new object */
for(var prop in target)
{
obj[prop] = target[prop];
}
return obj;
}
return false;
};
/* this will allow the classes to be extened.
@param (object) child = the child object to extend
@return (mixed) the new child prototype or false */
Class.extend = function(child)
{
/* the child constructor must be set to set
the parent static methods on the child */
var constructor = child && child.constructor? child.constructor : false;
if(constructor)
{
/* this will add the parent class to the
child class */
var parent = this.prototype;
constructor.prototype = extendClass(parent, child);
constructor.prototype.parent = parent;
/* this will add the static methods from the parent to
the child constructor. */
extendObject(this, constructor);
return constructor.prototype;
}
return false;
};