Método de llamada con prototipo de JavaScript


129

¿Es posible llamar al método base desde un método prototipo en JavaScript si se ha anulado?

MyClass = function(name){
    this.name = name;
    this.do = function() {
        //do somthing 
    }
};

MyClass.prototype.do = function() {  
    if (this.name === 'something') {
        //do something new
    } else {
        //CALL BASE METHOD
    }
};

¿Cuál es el método base? this.do = function () {} en el constructor?
Ionuț G. Stan

Respuestas:


200

No entendí qué es exactamente lo que está tratando de hacer, pero normalmente la implementación de un comportamiento específico del objeto se realiza de la siguiente manera:

function MyClass(name) {
    this.name = name;
}

MyClass.prototype.doStuff = function() {
    // generic behaviour
}

var myObj = new MyClass('foo');

var myObjSpecial = new MyClass('bar');
myObjSpecial.doStuff = function() {
    // do specialised stuff
    // how to call the generic implementation:
    MyClass.prototype.doStuff.call(this /*, args...*/);
}

1
No entiendo esto, vine aquí exactamente con el mismo propósito que markvpc. Quiero decir, quiero tener una especie de método "privado" que se utiliza de otros métodos en el prototipo. Su respuesta me obliga a crear una fábrica y me gustaría saber si hay alguna forma de evitarlo.
R01010010

10
No use palabras de "Clase" en JS porque crea confusión. No hay clases en JavaScript
Polaris

1
Esto solo funciona para objetos instanciados. ¿Qué sucede si desea anular todas las instancias?
supertonsky

¡Funciona para mi! Exactamente lo que necesitaba. Tengo 4 constructores que heredan de un padre. Dos de ellos necesitan anular y llamar a un método en el constructor base que es su prototipo. Esto me permitió exactamente eso.
binarygiant

3
No entiendo por qué esto tiene tantos votos positivos y está marcado como correcto. Esto solo anula el comportamiento para una instancia de la clase base, no todas las instancias de la subclase. Por lo tanto, esto no responde a la pregunta.
BlueRaja - Danny Pflughoeft

24

Bueno, una forma de hacerlo sería guardar el método base y luego llamarlo desde el método anulado, así

MyClass.prototype._do_base = MyClass.prototype.do;
MyClass.prototype.do = function(){  

    if (this.name === 'something'){

        //do something new

    }else{
        return this._do_base();
    }

};

11
Esto creará una recursión infinita.
Johan Tidén

3
He estado allí: recursión infinita.
jperelli

La cláusula else está llamando y ejecutando _do_base, que es una referencia a prototype.do. Como no ha cambiado ningún parámetro (o estado), el código volverá a entrar en lo demás, llamando nuevamente a _do_base.
Johan Tidén

10
@ JohanTidén _do_basealmacena una referencia a cualquier función definida anteriormente. Si no hubiera ninguno, entonces lo sería undefined. JavaScript no lo es: las makeexpresiones nunca se evalúan perezosamente como usted sugiere que lo son. Ahora, si el prototipo que se hereda también usó _do_base para almacenar lo que cree que es la implementación de su tipo base do, entonces tiene un problema. Entonces, sí, un cierre sería una forma mejor y segura de heredar para almacenar la referencia a la implementación de la función original si se usa este método, aunque eso requeriría usarlo Function.prototype.call(this).
binki

16

Me temo que su ejemplo no funciona como usted piensa. Esta parte:

this.do = function(){ /*do something*/ };

sobrescribe la definición de

MyClass.prototype.do = function(){ /*do something else*/ };

Dado que el objeto recién creado ya tiene una propiedad "do", no busca la cadena de prototipos.

La forma clásica de herencia en Javascript es torpe y difícil de entender. Sugeriría usar el patrón de herencia simple Douglas Crockfords en su lugar. Me gusta esto:

function my_class(name) {
    return {
        name: name,
        do: function () { /* do something */ }
    };
}

function my_child(name) {
    var me = my_class(name);
    var base_do = me.do;
    me.do = function () {
        if (this.name === 'something'){
            //do something new
        } else {
            base_do.call(me);
        }
    }
    return me;
}

var o = my_child("something");
o.do(); // does something new

var u = my_child("something else");
u.do(); // uses base function

En mi opinión, una forma mucho más clara de manejar objetos, constructores y herencia en javascript. Puedes leer más en Crockfords Javascript: Las partes buenas .


3
De hecho, es muy agradable, pero en realidad no se puede llamar base_docomo una función, porque de esa manera se pierde cualquier thisenlace en el dométodo original . Por lo tanto, la configuración del método base es un poco más compleja, especialmente si desea llamarlo utilizando el objeto base en thislugar del objeto secundario. Sugeriría algo parecido base_do.apply(me, arguments).
Giulio Piancastelli

Sólo me preguntaba, ¿por qué no se utiliza varpara base_do? ¿Es esto a propósito y, de ser así, por qué? Y, como dijo @GiulioPiancastelli, ¿esta ruptura thisvinculante base_do()o esa llamada heredará thisde su llamador?
binki

@binki actualicé el ejemplo. El vardebería estar allí. El uso de call(o apply) nos permite vincular thiscorrectamente.
Magnar

10

Sé que esta publicación es de hace 4 años, pero debido a mi experiencia en C #, estaba buscando una manera de llamar a la clase base sin tener que especificar el nombre de la clase, sino obtenerla mediante una propiedad en la subclase. Entonces, mi único cambio en la respuesta de Christoph sería

De esto:

MyClass.prototype.doStuff.call(this /*, args...*/);

A esto:

this.constructor.prototype.doStuff.call(this /*, args...*/);

66
No hagas esto, this.constructorpuede que no siempre apunte MyClass.
Bergi

Bueno, debería, a menos que alguien haya arruinado la configuración de la herencia. 'this' siempre debe referirse al objeto de nivel superior, y su propiedad 'constructor' siempre debe apuntar a su propia función de constructor.
Triynko

5

si define una función como esta (usando OOP)

function Person(){};
Person.prototype.say = function(message){
   console.log(message);
}

Hay dos formas de llamar a una función prototipo: 1) hacer una instancia y llamar a la función objeto

var person = new Person();
person.say('hello!');

y la otra forma es ... 2) está llamando a la función directamente desde el prototipo:

Person.prototype.say('hello there!');

5

Esta solución usa Object.getPrototypeOf

TestA es super que tiene getName

TestBes un hijo que anula getNamepero también tiene getBothNamesque llama tanto la superversión getNamecomo la childversión

function TestA() {
  this.count = 1;
}
TestA.prototype.constructor = TestA;
TestA.prototype.getName = function ta_gn() {
  this.count = 2;
  return ' TestA.prototype.getName is called  **';
};

function TestB() {
  this.idx = 30;
  this.count = 10;
}
TestB.prototype = new TestA();
TestB.prototype.constructor = TestB;
TestB.prototype.getName = function tb_gn() {
  return ' TestB.prototype.getName is called ** ';
};

TestB.prototype.getBothNames = function tb_gbn() {
  return Object.getPrototypeOf(TestB.prototype).getName.call(this) + this.getName() + ' this object is : ' + JSON.stringify(this);
};

var tb = new TestB();
console.log(tb.getBothNames());


4
function NewClass() {
    var self = this;
    BaseClass.call(self);          // Set base class

    var baseModify = self.modify;  // Get base function
    self.modify = function () {
        // Override code here
        baseModify();
    };
}

4

Una alternativa :

// shape 
var shape = function(type){
    this.type = type;
}   
shape.prototype.display = function(){
    console.log(this.type);
}
// circle
var circle = new shape('circle');
// override
circle.display = function(a,b){ 
    // call implementation of the super class
    this.__proto__.display.apply(this,arguments);
}

A pesar de que puede funcionar, no recomendaré esta solución. Hay muchas razones por las __proto__que debe evitarse el uso (que cambia el [[Prototipo]] `de un objeto). Utiliza la Object.create(...)ruta en su lugar.
KarelG

2

Si entiendo correctamente, desea que la funcionalidad de Base se realice siempre, mientras que una parte de ella debe dejarse a las implementaciones.

Es posible que reciba ayuda del patrón de diseño ' método de plantilla '.

Base = function() {}
Base.prototype.do = function() { 
    // .. prologue code
    this.impldo(); 
    // epilogue code 
}
// note: no impldo implementation for Base!

derived = new Base();
derived.impldo = function() { /* do derived things here safely */ }

1

Si conoce su superclase por su nombre, puede hacer algo como esto:

function Base() {
}

Base.prototype.foo = function() {
  console.log('called foo in Base');
}

function Sub() {
}

Sub.prototype = new Base();

Sub.prototype.foo = function() {
  console.log('called foo in Sub');
  Base.prototype.foo.call(this);
}

var base = new Base();
base.foo();

var sub = new Sub();
sub.foo();

Esto imprimirá

called foo in Base
called foo in Sub
called foo in Base

como se esperaba.


1

Otra forma con ES5 es atravesar explícitamente la cadena del prototipo usando Object.getPrototypeOf(this)

const speaker = {
  speak: () => console.log('the speaker has spoken')
}

const announcingSpeaker = Object.create(speaker, {
  speak: {
    value: function() {
      console.log('Attention please!')
      Object.getPrototypeOf(this).speak()
    }
  }
})

announcingSpeaker.speak()

0

No, necesitaría asignar diferentes funciones a la función do en el constructor y a la función do en los prototipos.


0

Además, si desea anular todas las instancias y no solo esa instancia especial, esta podría ayudar.

function MyClass() {}

MyClass.prototype.myMethod = function() {
  alert( "doing original");
};
MyClass.prototype.myMethod_original = MyClass.prototype.myMethod;
MyClass.prototype.myMethod = function() {
  MyClass.prototype.myMethod_original.call( this );
  alert( "doing override");
};

myObj = new MyClass();
myObj.myMethod();

resultado:

doing original
doing override

-1
function MyClass() {}

MyClass.prototype.myMethod = function() {
  alert( "doing original");
};
MyClass.prototype.myMethod_original = MyClass.prototype.myMethod;
MyClass.prototype.myMethod = function() {
  MyClass.prototype.myMethod_original.call( this );
  alert( "doing override");
};

myObj = new MyClass();
myObj.myMethod();
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.