Comment appeler une méthode parente depuis une classe enfant en javascript?

156

J'ai passé les dernières heures à essayer de trouver une solution à mon problème, mais cela semble désespéré.

Fondamentalement, j'ai besoin de savoir comment appeler une méthode parent à partir d'une classe enfant. Tout ce que j'ai essayé jusqu'à présent finit par ne pas fonctionner ou par écraser la méthode parente.

J'utilise le code suivant pour configurer la POO en 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);

Je dois d'abord appeler la méthode du parent, puis y ajouter des éléments supplémentaires dans la classe enfant.

Dans la plupart des langages POO, ce serait aussi simple que d'appeler parent.myMethod() Mais je ne peux vraiment pas comprendre comment c'est fait en javascript.

Toute aide est très appréciée, merci!

YemSalat
la source

Réponses:

196

Voici comment procéder: ParentClass.prototype.myMethod();

Ou si vous souhaitez l'appeler dans le contexte de l'instance actuelle, vous pouvez faire: ParentClass.prototype.myMethod.call(this)

Il en va de même pour l'appel d'une méthode parent à partir d'une classe enfant avec des arguments: ParentClass.prototype.myMethod.call(this, arg1, arg2, ..)* Astuce: utilisez apply()au lieu de call()pour passer des arguments sous forme de tableau.

YemSalat
la source
7
Si vous voulez l'appeler dans le contexte de l'instance actuelle, vous devez faire ParentClass.prototype.myMethod.apply() or ParentClass.prototype.myMethod.call () `, comme vous le faites avec votre constructeur.
JMM
3
En ajoutant simplement que si vous voulez appeler avec des arguments, ils vont dans la fonction apply ou call ( ParentClass.prototype.myMethod.call(this, arg1, arg2, arg3...);)
Gershom
Je ne comprends pas. Si j'appelle ParentClass.prototype.myMethod.call (this); de la méthode myMethod de ChildObject, j'ai une erreur "Uncaught TypeError: Cannot read property 'call' of undefined".
zhekaus
@zhekaus, cela voudrait dire que vous n'en avez pas myMethoddans votre classe.
YemSalat
2
J'utilise actuellement this.myFun = function () {} pour déclarer une méthode objet, donc appeler ParentClass.prototype.myFun.call (...) ne fonctionne pas, donc je dois utiliser CurrentClass.prototype.myFun.call ( ...). JS est ... une merde, nous devrions utiliser une vraie POO.
Loenix
156

Le style ES6 vous permet d'utiliser de nouvelles fonctionnalités, telles que le supermot-clé. supermot-clé, tout est question de contexte de classe parent, lorsque vous utilisez la syntaxe des classes ES6. À titre d'exemple très simple, passez à la caisse:

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

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

En outre, vous pouvez utiliser superpour appeler le constructeur parent:

class Foo {}

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

Et bien sûr, vous pouvez l'utiliser pour accéder aux propriétés de la classe parent super.prop. Alors, utilisez ES6 et soyez heureux.

Dmytro Medvid
la source
10
@ fsinisi90 Je crois que la question ne concerne pas les méthodes de classe du parent, mais plutôt les méthodes d'instance du parent qui ne peuvent tout simplement pas être appelées avec un super mot-clé à partir de ES6.
mcmlxxxiii
cela fonctionne aussi pour les méthodes qui ne sont pas statiques (testé avec Chrome, sans transpiling, pas essayé le mot-clé static)
Gianluca Casati
Pourquoi faut-il superl'appeler? Y a-t-il une équivalence dans «l'ancien» JS?
1252748
3
super () doit être appelé dans le constructeur de classe enfant avant toute autre chose.
user938363
1
@GianlucaCasati: vous ne pouvez utiliser que super()dans les méthodes statiques; on dirait que vous l'avez utilisé dans le constructeur.
ZzZombo
5

Eh bien, pour ce faire, vous n'êtes pas limité par l' Classabstraction d'ES6. L'accès aux méthodes prototypes du constructeur parent est possible via la __proto__propriété (je suis presque sûr qu'il y aura d'autres codeurs JS pour se plaindre qu'il est déprécié) qui est dépréciée mais en même temps découvert qu'il s'agit en fait d'un outil essentiel pour les besoins de sous-classification ( en particulier pour les besoins de sous-classification Array). Ainsi, bien que la __proto__propriété soit toujours disponible dans tous les principaux moteurs JS que je connais, ES6 a introduit la Object.getPrototypeOf()fonctionnalité en plus. L' super()outil dans leClass abstraction en est un sucre syntaxique.

Donc, dans le cas où vous n'avez pas accès au nom du constructeur parent et que vous ne voulez pas utiliser l' Classabstraction, vous pouvez toujours faire comme suit;

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
la source
4

En cas de niveau d'héritage multiple, cette fonction peut être utilisée comme méthode super () dans d'autres langues. Voici un violon de démonstration , avec quelques tests, vous pouvez l'utiliser comme ceci, à l'intérieur de votre méthode:call_base(this, 'method_name', arguments);

Il utilise des fonctions ES assez récentes, une compatibilité avec les anciens navigateurs n'est pas garantie. Testé dans 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
la source
2

Que diriez-vous de quelque chose basé sur l'idée de Douglas Crockford:

    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
la source
2

Voici un bon moyen pour les objets enfants d'avoir accès aux propriétés et méthodes parentes à l'aide de la chaîne de prototypes de JavaScript, et il est compatible avec Internet Explorer. JavaScript recherche des méthodes dans la chaîne de prototypes et nous voulons que la chaîne de prototypes de l'enfant ressemble à ceci:

Instance enfant -> Prototype de l'enfant (avec méthodes Child) -> Prototype du parent (avec méthodes Parent) -> Prototype d'objet -> null

Les méthodes enfants peuvent également appeler des méthodes parent masquées, comme indiqué dans les trois astérisques *** ci-dessous.

Voici comment:

//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
la source
1

Il existe une solution beaucoup plus simple et plus compacte pour la recherche de prototypes à plusieurs niveaux, mais elle nécessite une Proxyassistance. Utilisation: SUPER(<instance>).<method>(<args>)par exemple, en supposant deux classes Aet B extends Aavec la méthode 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
la source
0

Bien que vous puissiez appeler la méthode parent par le prototype du parent, vous devrez transmettre l'instance enfant actuelle pour utiliser call , applyou bindméthode. La bindméthode créera une nouvelle fonction, je ne le recommande donc pas si vous vous souciez des performances, sauf qu'elle n'est appelée qu'une seule fois.

Vous pouvez également remplacer la méthode enfant et placer la méthode parent sur l'instance tout en appelant la méthode enfant d'origine.

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
la source