Наследование JS: вызов родительской функции из дочерней функции

должно быть что-то, чего я не понимаю в объектной модели JS.

из этих ресурсов:

3 ответов


Отличный вопрос, потребовалось немного тестирования и исследований, чтобы узнать это.

выявление странного поведения

я немного изменил ваш код, чтобы узнать, какая функция вызывается, когда:

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.

таким образом, первая строка вывода означает, что текущий this контекст действительно childInstance. но второе-ни childInstance, ни Parent.prototype:

  1. звонок (myF of childInstance): this относится к childInstance. ищет [[Prototype]] на childInstance, что это Child.prototype, а не Parent.prototype. Вывод: "я что, сошел с ума??'
  2. вызов (myF of Child.prototype): this относится к Child.prototype, который является childInstances [[Прототип]] Собственность. Итак, второй призыв Object.getPrototypeOf(this).myF(); наконец возвращает Parent.prototype (вроде). Вывод: "я что, сошел с ума??'

  3. звонок (myF of Parent.prototype экземпляр создан Object.create): наконец-то myF на родительском называется. Вывод: 'derp'

поскольку console.log("did I derp??") после myF функции выход в обратном порядке. на следующем рисунке показано, как проходит код:

enter image description here

так что ваше предположение о что?!--18--> относится, был неправ.

решение в ES5

By @LukeP: https://jsfiddle.net/Lpxq78bs/28/

альтернативное решение в ES6

чтобы избежать этой путаницы, и поскольку вы работаете с классическим шаблоном наследования, вы можете взглянуть на классы ЕС6. Ниже приведен пример того, что вы пытаетесь сделать:

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();

я надеюсь, что это помогает в понимании Что происходит.


ваш код работает так , как ожидалось, вывод, который вы получаете, из-за 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 будет печатать баттхерт потому что myF метод Parent.prototype вызывается. После этого вторая строка будет печатать я баттхерт.

Шаг 3: затем функция возвращается Шаг 1, чтобы выполнить вторую строку, которая снова печатает я баттхерт


Если вам нужно работать с наследованием в ES5, я сделал оболочку, которая упрощает расширение и вызов родительских методов. Никаких рамок не требуется.

скрипка для рабочего кода здесь:https://jsfiddle.net/wcrwLmrk/13/

вот как работает код:

/* extend the base class to the new parent class and pass 
an object with the new properties and methods */
var ParentClass = Class.extend(  
{ 
    // reset the constructor to the correct constructor
    constructor: function() 
    { 

    },

    callName: function(arg)
    { 
        console.log('parent ' + arg); 
    }
}); 

/* extend the parent class to the new child class and pass 
an object with the new properties and methods */
var ChildClass = ParentClass.extend(  
{ 
    // reset the constructor to the correct constructor
    constructor: function() 
    { 
        ParentClass.call(this); 
    },

    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 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)
{  
    if(!child)
    { 
        return false; 
    } 

    var parent = this.prototype; 

    /* the child constructor must be set to set 
    the parent static methods on the child */ 
    var constructor = child && child.constructor? child.constructor : false; 
    if(child.hasOwnProperty('constructor') === false)
    { 
        constructor = function()
        {
            var args = arguments; 
            parent.constructor.apply(this, args); 
        }; 
    }

    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; 
};