Вызов метода с использованием прототипа 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.