Jak wywołać metodę nadrzędną z klasy potomnej w javascript?

156

Spędziłem ostatnie kilka godzin próbując znaleźć rozwiązanie mojego problemu, ale wydaje się to beznadziejne.

Zasadniczo muszę wiedzieć, jak wywołać metodę nadrzędną z klasy podrzędnej. Wszystkie rzeczy, które próbowałem do tej pory, kończą się albo nie działają, albo nadpisują metodę nadrzędną.

Używam następującego kodu, aby skonfigurować OOP w 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);

Najpierw muszę wywołać metodę rodzica, a następnie dodać do niej więcej rzeczy w klasie podrzędnej.

W większości języków OOP byłoby to tak proste, jak wywołanie. parent.myMethod() Ale naprawdę nie mogę pojąć, jak to się robi w javascript.

Każda pomoc jest mile widziana, dziękuję!

YemSalat
źródło

Odpowiedzi:

196

Oto jak to się robi: ParentClass.prototype.myMethod();

Lub jeśli chcesz wywołać to w kontekście bieżącej instancji, możesz zrobić: ParentClass.prototype.myMethod.call(this)

To samo dotyczy wywołania metody nadrzędnej z klasy potomnej z argumentami: ParentClass.prototype.myMethod.call(this, arg1, arg2, ..)* Wskazówka: użyj apply()zamiast, call()aby przekazać argumenty jako tablicę.

YemSalat
źródło
7
Jeśli chcesz wywołać go w kontekście bieżącej instancji, musisz wykonać ParentClass.prototype.myMethod.apply() or ParentClass.prototype.myMethod.call () `, tak jak robisz to z konstruktorem.
JMM,
3
Po prostu dodając, że jeśli chcesz wywołać z argumentami, wchodzą one do funkcji apply lub call ( ParentClass.prototype.myMethod.call(this, arg1, arg2, arg3...);)
Gershom
Nie rozumiem. Jeśli wywołam ParentClass.prototype.myMethod.call (this); z myMethod ChildObject, otrzymałem błąd „Uncaught TypeError: Cannot read property 'call' of undefined".
zhekaus
@zhekaus, oznaczałoby to, że nie masz myMethodna swojej klasie.
YemSalat
2
Obecnie używam this.myFun = function () {} do zadeklarowania metody obiektu, więc wywołanie ParentClass.prototype.myFun.call (...) nie działa, więc muszę użyć CurrentClass.prototype.myFun.call ( ...). JS to ... bzdura, powinniśmy użyć prawdziwego OOP.
Loenix
156

Styl ES6 umożliwia korzystanie z nowych funkcji, takich jak supersłowo kluczowe. supersłowo kluczowe dotyczy kontekstu klasy nadrzędnej, gdy używasz składni klas ES6. Jako bardzo prosty przykład, do kasy:

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

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

Możesz również użyć superdo wywołania konstruktora nadrzędnego:

class Foo {}

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

I oczywiście możesz go użyć, aby uzyskać dostęp do właściwości klasy nadrzędnej super.prop. Więc używaj ES6 i bądź szczęśliwy.

Dmytro Medvid
źródło
10
@ fsinisi90 Myślę, że pytanie nie dotyczy metod klas rodzica, ale raczej metod instancji rodzica, których po prostu nie można wywołać za pomocą słowa kluczowego super od ES6.
mcmlxxxiii,
działa również dla metod, które nie są statyczne (przetestowane z Chrome, bez transpiliacji, nie wypróbowałem statycznego słowa kluczowego)
Gianluca Casati
Dlaczego tego potrzebuje super to nazwać? Czy istnieje odpowiednik w „starym” JS?
1252748
3
super () musi zostać wywołana w konstruktorze klasy potomnej, zanim cokolwiek innego.
user938363
1
@GianlucaCasati: możesz używać tylko super()w metodach statycznych; wygląda na to, że użyłeś go w konstruktorze.
ZzZombo
5

Aby to zrobić, nie musisz ograniczać się do Classabstrakcji ES6. Dostęp do prototypowych metod konstruktora nadrzędnego jest możliwy poprzez __proto__właściwość (jestem prawie pewien, że inni programiści JS będą narzekać, że jest zdeprecjonowana), która jest zdeprecjonowana, ale jednocześnie odkryła, że ​​jest to w rzeczywistości niezbędne narzędzie dla potrzeb podklasowania ( szczególnie dla potrzeb podklas Array). Więc chociaż ta __proto__właściwość jest nadal dostępna we wszystkich głównych silnikach JS, jakie znam, ES6 wprowadził dodatkową Object.getPrototypeOf()funkcjonalność. super()Narzędziem w Classabstrakcji jest cukier syntaktyczny tego.

Więc jeśli nie masz dostępu do nazwy konstruktora nadrzędnego i nie chcesz używać rozszerzenia Class abstrakcji, możesz nadal wykonać następujące czynności;

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
źródło
4

W przypadku wielokrotnego dziedziczenia funkcja ta może być używana jako metoda super () w innych językach. Oto przykładowe skrzypce , z kilkoma testami, możesz tego użyć w ten sposób, wewnątrz swojej metody użyj:call_base(this, 'method_name', arguments);

Wykorzystuje całkiem nowe funkcje ES, kompatybilność ze starszymi przeglądarkami nie jest gwarantowana. Testowany w 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
źródło
2

A co z czymś opartym na pomyśle Douglasa Crockforda:

    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
źródło
2

Oto dobry sposób, aby obiekty podrzędne miały dostęp do właściwości i metod nadrzędnych przy użyciu łańcucha prototypów JavaScript i jest kompatybilny z Internet Explorerem. JavaScript przeszukuje łańcuch prototypów w poszukiwaniu metod i chcemy, aby łańcuch prototypów dziecka wyglądał następująco:

Instancja dziecka -> Prototyp dziecka (z metodami Child) -> Prototyp rodzica (z metodami Parent) -> Prototyp obiektu -> null

Metody potomne mogą również wywoływać ukryte metody nadrzędne, jak pokazano poniżej przy trzech gwiazdkach ***.

Oto jak:

//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
źródło
1

Istnieje znacznie łatwiejsze i bardziej kompaktowe rozwiązanie do wielopoziomowego wyszukiwania prototypów, ale wymaga ono Proxyobsługi. Zastosowanie: SUPER(<instance>).<method>(<args>)na przykład, zakładając dwie klasy Ai B extends Ametodą 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
źródło
0

Chociaż można wywołać metodę nadrzędnej przez prototyp rodzica, trzeba będzie przekazać bieżące wystąpienie dziecko za korzystanie call, applylub bindmetody. bindMetoda będzie tworzyć nową funkcję, więc nie polecam, że jeśli zależy Ci na wydajności oprócz niego wywoływana tylko raz.

Alternatywnie możesz zastąpić metodę potomną i umieścić metodę nadrzędną w instancji podczas wywoływania oryginalnej metody potomnej.

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
źródło