Наследование 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; 
};