Вызов метода с использованием прототипа JavaScript


129

Можно ли вызвать базовый метод из метода-прототипа в JavaScript, если он был переопределен?

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

Какой базовый метод? this.do = function () {} в конструкторе?
Ionu G. Stan

Ответы:


200

Я не понял, что именно вы пытаетесь сделать, но обычно реализация объектно-ориентированного поведения выполняется в следующих направлениях:

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
Я не понимаю, я пришел сюда с той же целью, что и markvpc. Я имею в виду, я хочу иметь своего рода «частный» метод, который используется из других методов в прототипе. Ваш ответ вынуждает меня создать фабрику, и я хотел бы знать, есть ли способ избежать этого.
R01010010

10
Не используйте слова «класс» в JS, потому что это создает путаницу. В JavaScript нет классов
Polaris

1
Это работает только для созданных объектов. Что, если вы хотите переопределить все экземпляры?
supertonsky

Работает для меня! Именно то, что мне нужно. У меня есть 4 конструктора, которые наследуются от родителя. Двум из них необходимо переопределить и вызвать метод базового конструктора, который является их прототипом. Это позволило мне именно это.
binarygiant 09

3
Я не понимаю, почему у него так много голосов и он помечен как правильный. Это переопределяет поведение только для одного экземпляра базового класса, а не для всех экземпляров подкласса. Таким образом, это не отвечает на вопрос.
BlueRaja - Дэнни Пфлугхофт

24

Ну, один из способов сделать это - сохранить базовый метод, а затем вызвать его из замещающего метода, например

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

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

        //do something new

    }else{
        return this._do_base();
    }

};

11
Это создаст бесконечную рекурсию.
Johan Tidén

3
Я был там: Бесконечная рекурсия.
jperelli

Предложение else вызывает и запускает _do_base, который является ссылкой на prototype.do. Поскольку никакие параметры (или состояние) не изменились, код снова перейдет в else, снова вызывая _do_base.
Йохан Тиден

10
@ JohanTidén _do_baseсохраняет ссылку на любую функцию, которая была определена ранее. Если бы его не было, то было бы undefined. JavaScript не работает - makeвыражения никогда не оцениваются лениво, как вы предполагаете. Теперь, если прототип, унаследованный от, также использовал _do_base для хранения того, что, по его мнению, является реализацией его базового типа do, тогда у вас действительно есть проблема. Итак, да, замыкание было бы лучшим безопасным для наследования способом сохранить ссылку на исходную реализацию функции при использовании этого метода, хотя для этого потребуется использовать Function.prototype.call(this).
binki

16

Боюсь, ваш пример не работает так, как вы думаете. Эта часть:

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

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

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

Поскольку вновь созданный объект уже имеет свойство «do», он не ищет цепочку прототипов.

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

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

На мой взгляд, это гораздо более понятный способ обработки объектов, конструкторов и наследования в javascript. Вы можете прочитать больше в Crockfords Javascript: Хорошие части .


3
Действительно, очень красиво, но вы не можете действительно вызывать base_doкак функцию, потому что таким образом вы теряете любую thisпривязку в исходном doметоде. Итак, настройка базового метода немного сложнее, особенно если вы хотите вызвать его, используя базовый объект thisвместо дочернего объекта. Я бы предложил что-нибудь похожее base_do.apply(me, arguments).
Джулио Пьянкастелли

Просто интересно, почему бы вам не использовать varдля base_do? Это сделано специально, и если да, то почему? И, как сказал @GiulioPiancastelli, нарушает ли это thisпривязку внутри base_do()или этот вызов наследует thisот вызывающего?
binki

@binki Я обновил пример. varДолжен быть там. Использование call(или apply) позволяет нам thisправильно выполнять привязку .
Magnar

10

Я знаю, что это сообщение написано 4 года назад, но из-за моего опыта работы с C # я искал способ вызвать базовый класс без необходимости указывать имя класса, а получить его с помощью свойства в подклассе. Поэтому мое единственное изменение в ответе Кристофа будет

Из этого:

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

К этому:

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

6
Не делайте этого, this.constructorможет не всегда указывать на MyClass.
Bergi

Ну, должно быть, если только кто-то не облажался с настройкой наследования. 'this' всегда должно ссылаться на объект верхнего уровня, а его свойство 'constructor' всегда должно указывать на его собственную функцию-конструктор.
Трийнко

5

если вы определяете такую ​​функцию (используя ООП)

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

есть два способа вызвать функцию-прототип: 1) создать экземпляр и вызвать функцию объекта:

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

а другой способ ... 2) вызывает функцию прямо из прототипа:

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

5

Это решение использует Object.getPrototypeOf

TestA супер, что есть getName

TestBявляется дочерним элементом, который переопределяет, getNameно также имеет, getBothNamesчто вызывает superверсию, getNameа также childверсию

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

Альтернатива :

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

Несмотря на то, что это может сработать, я не рекомендую это решение. Есть много причин, по которым __proto__следует избегать использования (которое изменяет [[Prototype]] `объекта). Object.create(...)Вместо этого используйте маршрут.
KarelG

2

Если я правильно понимаю, вы хотите, чтобы базовая функциональность выполнялась всегда, а часть ее следует оставить для реализации.

Вам может помочь шаблон проектирования « шаблонный метод ».

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

Если вы знаете свой суперкласс по имени, вы можете сделать что-то вроде этого:

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

Это напечатает

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

как и ожидалось.


1

Другой способ с ES5 - явное прохождение цепочки прототипов, используя 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

Нет, вам нужно будет дать функции do в конструкторе и функции do в прототипе разные имена.


0

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

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

результат:

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();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.