Спосіб виклику за допомогою прототипу 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
Не використовуйте слова "Class" у JS, оскільки це створює плутанину. Немає занять у JavaScript
Polaris

1
Це працює лише для об'єктів, створених інстанцією. Що робити, якщо ви хочете змінити всі екземпляри?
супертонський

Для мене працює! Саме те, що мені було потрібно. У мене є 4 конструктори, які успадковують від батьків. Дві з них потрібно переосмислити і викликати метод на базовому конструкторі, який є їх прототипом. Це дозволило мені саме це.
бінаргігант

3
Я не розумію, чому ця кількість таких результатів і позначена як правильна. Це лише переосмислює поведінку для одного примірника базового класу, а не для всіх примірників підкласу. Таким чином, це не дає відповіді на запитання.
BlueRaja - Danny Pflughoeft

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
Це створить нескінченну рекурсію.
Йохан Тіден

3
Я там був: Нескінченна рекурсія.
jperelli

Інший пункт викликає і працює _do_base, що є посиланням на prototype.do. Оскільки жодні параметри (або стан) не змінилися, код знову перейде в інше, знову викликаючи _do_base.
Йохан Тіден

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

16

Боюся, що ваш приклад працює не так, як ви думаєте. Ця частина:

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

замінює визначення

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

Оскільки новостворений об’єкт вже має властивість "робити", він не шукає прототиповий ланцюг.

Класична форма успадкування в 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 Я оновив приклад. varПовинен бути там. Використання call(або apply) дозволяє нам thisправильно пов’язувати .
Магнар

10

Я знаю, що ця публікація починається з 4 років тому, але через мій фон C # я шукав спосіб викликати базовий клас без необхідності вказувати ім’я класу, а скоріше отримати його за властивістю підкласу. Тож моя єдина зміна відповіді Крістофа була б

Від цього:

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

До цього:

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

6
Не робіть цього, this.constructorможе не завжди вказувати MyClass.
Бергі

Що ж, слід, якщо хтось не накрутив установку спадщини. "це" завжди має стосуватися об'єкта верхнього рівня, а його властивість "конструктор" завжди має вказувати на власну функцію конструктора.
Трайко

5

якщо ви визначаєте функцію, подібну до цієї (використовуючи OOP)

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__слід уникати використання (що змінює [[прототип]] `об'єкта). Будь ласка, скористайтеся Object.create(...)маршрутом.
KarelG

2

Якщо я правильно розумію, ви хочете, щоб функціонал Base завжди виконувався, тоді як фрагмент його слід залишити для реалізації.

Можливо, вам допоможе шаблон дизайну " метод шаблону ".

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.