Как вызвать родительский метод из дочернего класса в JavaScript?

156

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

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

Я использую следующий код для настройки ООП в JavaScript:

// SET UP OOP
// surrogate constructor (empty function)
function surrogateCtor() {}

function extend(base, sub) {
    // copy the prototype from the base to setup inheritance
    surrogateCtor.prototype = base.prototype;
    sub.prototype = new surrogateCtor();
    sub.prototype.constructor = sub;
}

// parent class
function ParentObject(name) {
    this.name = name;
}
// parent's methods
ParentObject.prototype = {
    myMethod: function(arg) {
        this.name = arg;
    }
}

// child
function ChildObject(name) {
    // call the parent's constructor
    ParentObject.call(this, name);
    this.myMethod = function(arg) {
        // HOW DO I CALL THE PARENT METHOD HERE?
        // do stuff
    }
}

// setup the prototype chain
extend(ParentObject, ChildObject);

Мне нужно сначала вызвать метод родителя, а затем добавить еще кое-что в дочерний класс.

В большинстве языков ООП это было бы так просто, как вызов. parent.myMethod() Но я действительно не могу понять, как это делается в javascript.

Любая помощь очень ценится, спасибо!

YemSalat
источник

Ответы:

196

Вот как это делается: ParentClass.prototype.myMethod();

Или, если вы хотите вызвать его в контексте текущего экземпляра, вы можете сделать: ParentClass.prototype.myMethod.call(this)

То же самое касается вызова родительского метода из дочернего класса с аргументами: ParentClass.prototype.myMethod.call(this, arg1, arg2, ..)* Подсказка: используйте apply()вместо call()передачи аргументов в виде массива.

YemSalat
источник
7
Если вы хотите вызвать его в контексте текущего экземпляра, вы должны сделать ParentClass.prototype.myMethod.apply() or ParentClass.prototype.myMethod.call () `, как вы делаете это с вашим конструктором.
JMM
3
Просто добавив, что если вы хотите вызывать с аргументами, они идут внутри функции apply или call ( ParentClass.prototype.myMethod.call(this, arg1, arg2, arg3...);)
Gershom
Я не понимаю Если я вызываю ParentClass.prototype.myMethod.call (this); из myMethod объекта ChildObject я получил ошибку "Uncaught TypeError: Невозможно прочитать свойство 'call' of undefined".
Жекаус
@zhekaus, это будет означать, что у вас нет myMethodв вашем классе.
YemSalat
2
В настоящее время я использую this.myFun = function () {} для объявления метода объекта, поэтому вызов ParentClass.prototype.myFun.call (...) не работает, поэтому я должен использовать CurrentClass.prototype.myFun.call ( ...). JS ... дерьмо, мы должны использовать настоящий ООП.
Loenix
156

Стиль ES6 позволяет использовать новые функции, такие как superключевое слово. superКлючевое слово это все о контексте родительского класса, когда вы используете синтаксис классов ES6. В качестве очень простого примера, оформить заказ:

class Foo {
    static classMethod() {
        return 'hello';
    }
}

class Bar extends Foo {
    static classMethod() {
        return super.classMethod() + ', too';
    }
}
Bar.classMethod(); // 'hello, too'

Также вы можете использовать superдля вызова родительского конструктора:

class Foo {}

class Bar extends Foo {
    constructor(num) {
        let tmp = num * 2; // OK
        this.num = num; // ReferenceError
        super();
        this.num = num; // OK
    }
}

И, конечно, вы можете использовать его для доступа к свойствам родительского класса super.prop. Итак, используйте ES6 и будьте счастливы.

Дмитрий Медвид
источник
10
@ fsinisi90 Я полагаю, что вопрос не в методах класса родителя, а в методах экземпляра родителя, которые нельзя просто вызвать с помощью ключевого слова super с ES6.
mcmlxxxiii
это работает также для методов, которые не являются статичными (протестировано с Chrome, без транспиляции, не пробовало ключевое слово static)
Джанлука Казати
Почему это нужно superназывать? Есть ли эквивалентность в «старых» JS?
1252748
3
super () должен вызываться в конструкторе дочернего класса раньше всего.
user938363
1
@GianlucaCasati: вы можете использовать только super()в статических методах; Похоже, вы использовали его в конструкторе.
ZzZombo
5

Ну, чтобы сделать это, вы не ограничены Classабстракцией ES6. Доступ к методам прототипа родительского конструктора возможен с помощью __proto__свойства (я уверен, что будут и другие JS-кодеры, которые будут жаловаться на то, что оно устарело), ​​которое устарело, но в то же время обнаружило, что оно действительно является важным инструментом для нужд подкласса ( особенно для нужд подклассов массива, хотя). Таким образом, в то время как __proto__свойство все еще доступно во всех основных движках JS, которые я знаю, ES6 представил Object.getPrototypeOf()функциональность поверх него. super()Инструмент вClass абстракции является синтаксическим сахаром этого.

Поэтому, если у вас нет доступа к имени родительского конструктора и вы не хотите использовать Classабстракцию, вы все равно можете сделать следующее;

function ChildObject(name) {
    // call the parent's constructor
    ParentObject.call(this, name);
    this.myMethod = function(arg) {
    //this.__proto__.__proto__.myMethod.call(this,arg);
    Object.getPrototypeOf(Object.getPrototypeOf(this)).myMethod.call(this,arg);
    }
}
Redu
источник
4

В случае множественного уровня наследования эта функция может использоваться как метод super () в других языках. Вот демо скрипка , с некоторыми тестами, вы можете использовать ее следующим образом:call_base(this, 'method_name', arguments);

В нем используются новейшие функции ES, совместимость со старыми браузерами не гарантируется. Протестировано в IE11, FF29, CH35.

/**
 * Call super method of the given object and method.
 * This function create a temporary variable called "_call_base_reference",
 * to inspect whole inheritance linage. It will be deleted at the end of inspection.
 *
 * Usage : Inside your method use call_base(this, 'method_name', arguments);
 *
 * @param {object} object The owner object of the method and inheritance linage
 * @param {string} method The name of the super method to find.
 * @param {array} args The calls arguments, basically use the "arguments" special variable.
 * @returns {*} The data returned from the super method.
 */
function call_base(object, method, args) {
    // We get base object, first time it will be passed object,
    // but in case of multiple inheritance, it will be instance of parent objects.
    var base = object.hasOwnProperty('_call_base_reference') ? object._call_base_reference : object,
    // We get matching method, from current object,
    // this is a reference to define super method.
            object_current_method = base[method],
    // Temp object wo receive method definition.
            descriptor = null,
    // We define super function after founding current position.
            is_super = false,
    // Contain output data.
            output = null;
    while (base !== undefined) {
        // Get method info
        descriptor = Object.getOwnPropertyDescriptor(base, method);
        if (descriptor !== undefined) {
            // We search for current object method to define inherited part of chain.
            if (descriptor.value === object_current_method) {
                // Further loops will be considered as inherited function.
                is_super = true;
            }
            // We already have found current object method.
            else if (is_super === true) {
                // We need to pass original object to apply() as first argument,
                // this allow to keep original instance definition along all method
                // inheritance. But we also need to save reference to "base" who
                // contain parent class, it will be used into this function startup
                // to begin at the right chain position.
                object._call_base_reference = base;
                // Apply super method.
                output = descriptor.value.apply(object, args);
                // Property have been used into super function if another
                // call_base() is launched. Reference is not useful anymore.
                delete object._call_base_reference;
                // Job is done.
                return output;
            }
        }
        // Iterate to the next parent inherited.
        base = Object.getPrototypeOf(base);
    }
}
weeger
источник
2

Как насчет чего-то, основанного на идее Дугласа Крокфорда:

    function Shape(){}

    Shape.prototype.name = 'Shape';

    Shape.prototype.toString = function(){
        return this.constructor.parent
            ? this.constructor.parent.toString() + ',' + this.name
            : this.name;
    };


    function TwoDShape(){}

    var F = function(){};

    F.prototype = Shape.prototype;

    TwoDShape.prototype = new F();

    TwoDShape.prototype.constructor = TwoDShape;

    TwoDShape.parent = Shape.prototype;

    TwoDShape.prototype.name = '2D Shape';


    var my = new TwoDShape();

    console.log(my.toString()); ===> Shape,2D Shape
Alessandro
источник
2

Вот хороший способ для дочерних объектов иметь доступ к родительским свойствам и методам, используя цепочку прототипов JavaScript, и он совместим с Internet Explorer. JavaScript ищет в цепочке прототипов методы, и мы хотим, чтобы цепочка прототипов дочернего элемента выглядела так:

Дочерний экземпляр -> Дочерний прототип (с дочерними методами) -> Родительский прототип (с родительскими методами) -> Объектный прототип -> нуль

Дочерние методы также могут вызывать теневые родительские методы, как показано на трех звездочках *** ниже.

Вот как:

//Parent constructor
function ParentConstructor(firstName){
    //add parent properties:
    this.parentProperty = firstName;
}

//add 2 Parent methods:
ParentConstructor.prototype.parentMethod = function(argument){
    console.log(
            "Parent says: argument=" + argument +
            ", parentProperty=" + this.parentProperty +
            ", childProperty=" + this.childProperty
    );
};

ParentConstructor.prototype.commonMethod = function(argument){
    console.log("Hello from Parent! argument=" + argument);
};

//Child constructor    
function ChildConstructor(firstName, lastName){
    //first add parent's properties
    ParentConstructor.call(this, firstName);

    //now add child's properties:
    this.childProperty = lastName;
}

//insert Parent's methods into Child's prototype chain
var rCopyParentProto = Object.create(ParentConstructor.prototype);
rCopyParentProto.constructor = ChildConstructor;
ChildConstructor.prototype = rCopyParentProto;

//add 2 Child methods:
ChildConstructor.prototype.childMethod = function(argument){
    console.log(
            "Child says: argument=" + argument +
            ", parentProperty=" + this.parentProperty +
            ", childProperty=" + this.childProperty
    );
};

ChildConstructor.prototype.commonMethod = function(argument){
    console.log("Hello from Child! argument=" + argument);

    // *** call Parent's version of common method
    ParentConstructor.prototype.commonMethod(argument);
};

//create an instance of Child
var child_1 = new ChildConstructor('Albert', 'Einstein');

//call Child method
child_1.childMethod('do child method');

//call Parent method
child_1.parentMethod('do parent method');

//call common method
child_1.commonMethod('do common method');

CaptureWiz
источник
1

Для многоуровневого поиска прототипов существует гораздо более простое и компактное решение, но оно требует Proxyподдержки. Использование: SUPER(<instance>).<method>(<args>)например, если предположить два класса Aи B extends Aс помощью метода m: SUPER(new B).m().

function SUPER(instance) {
    return new Proxy(instance, {
        get(target, prop) {
            return Object.getPrototypeOf(Object.getPrototypeOf(target))[prop].bind(target);
        }
    });
}
ZzZombo
источник
0

В то время как вы можете вызвать родительский метод , с помощью прототипа родителя, вам нужно будет пройти текущий экземпляр ребенка для использования call, applyили bindметода. bindМетод будет создавать новую функцию , поэтому я не рекомендую, если вы заботитесь о производительности , кроме него вызывается только один раз.

В качестве альтернативы вы можете заменить дочерний метод и поместить родительский метод в экземпляр при вызове исходного дочернего метода.

function proxy(context, parent){
  var proto = parent.prototype;
  var list = Object.getOwnPropertyNames(proto);
  
  var child = {};
  for(var i=0; i<list.length; i++){
    var key = list[i];

    // Create only when child have similar method name
    if(context[key] !== proto[key]){
      child[key] = context[key];
      context[key] = function(){
        context.super = proto[key];
        return child[key].apply(context, arguments);
      }
    }
  }
}

// ========= The usage would be like this ==========

class Parent {
  first = "Home";

  constructor(){
    console.log('Parent created');
  }

  add(arg){
    return this.first + ", Parent "+arg;
  }
}

class Child extends Parent{
  constructor(b){
    super();
    proxy(this, Parent);
    console.log('Child created');
  }

  // Comment this to call method from parent only
  add(arg){
    return this.super(arg) + ", Child "+arg;
  }
}

var family = new Child();
console.log(family.add('B'));

StefansArya
источник