Спосіб виклику за допомогою прототипу 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.