Wywołanie metody wykorzystującej prototyp JavaScript


129

Czy można wywołać metodę podstawową z metody prototypowej w JavaScript, jeśli została nadpisana?

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

Jaka jest podstawowa metoda? this.do = function () {} w konstruktorze?
Ionuț G. Stan

Odpowiedzi:


201

Nie rozumiem, co dokładnie próbujesz zrobić, ale zwykle implementowanie zachowania specyficznego dla obiektu odbywa się w następujący sposób:

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
Nie rozumiem tego, przyszedłem tutaj dokładnie w tym samym celu, co markvpc. Chodzi mi o to, że chcę mieć rodzaj „prywatnej” metody, która jest używana z innych metod w prototypie. Twoja odpowiedź zmusza mnie do stworzenia fabryki i chciałbym wiedzieć, czy jest jakiś sposób, aby tego uniknąć.
R01010010

10
Nie używaj słów „klasy” w JS, ponieważ powoduje to zamieszanie. W JavaScript nie ma zajęć
Polaris

1
Działa to tylko dla obiektów, dla których utworzono instancję. A co, jeśli chcesz zastąpić wszystkie wystąpienia?
supertonsky

Pracuje dla mnie! Dokładnie to, czego potrzebowałem. Mam 4 konstruktory, które dziedziczą po rodzicu. Dwa z nich muszą przesłonić i wywołać metodę na konstruktorze podstawowym, który jest ich prototypem. To pozwoliło mi dokładnie to zrobić.
binarygiant

3
Nie rozumiem, dlaczego ma to tak wiele głosów za i jest oznaczone jako poprawne. To przesłania zachowanie tylko dla jednego wystąpienia klasy bazowej, a nie dla wszystkich wystąpień podklasy. Zatem to nie odpowiada na pytanie.
BlueRaja - Danny Pflughoeft

24

Cóż, jednym ze sposobów byłoby zapisanie metody podstawowej, a następnie wywołanie jej z metody nadpisanej

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

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

        //do something new

    }else{
        return this._do_base();
    }

};

11
Spowoduje to nieskończoną rekurencję.
Johan Tidén,

3
Byłem tam: nieskończona rekurencja.
jperelli

Klauzula else wywołuje i uruchamia _do_base, które jest odwołaniem do prototype.do. Ponieważ żadne parametry (ani stan) nie uległy zmianie, kod ponownie przejdzie do else, ponownie wywołując _do_base.
Johan Tidén

10
@ JohanTidén _do_baseprzechowuje odniesienie do dowolnej funkcji zdefiniowanej wcześniej. Gdyby go nie było, to byłoby undefined. JavaScript nie jest - makewyrażenia nigdy nie są oceniane leniwie, tak jak sugerujesz. Teraz, jeśli dziedziczony prototyp również używał _do_base do przechowywania tego, co uważa za implementację swojego typu podstawowego do, to masz problem. Więc tak, zamknięcie byłoby lepszym, bezpiecznym dla dziedziczenia sposobem przechowywania odniesienia do oryginalnej implementacji funkcji, jeśli używa się tej metody - chociaż wymagałoby to użycia Function.prototype.call(this).
binki

16

Obawiam się, że twój przykład nie działa tak, jak myślisz. Ta część:

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

zastępuje definicję

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

Ponieważ nowo utworzony obiekt ma już właściwość „do”, nie sprawdza łańcucha prototypów.

Klasyczna forma dziedziczenia w Javascript jest niezręczna i trudna do zrozumienia. Sugerowałbym zamiast tego użycie prostego wzoru dziedziczenia Douglasa Crockforda. Lubię to:

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

Moim zdaniem dużo jaśniejszy sposób obsługi obiektów, konstruktorów i dziedziczenia w javascript. Możesz przeczytać więcej w Crockfords Javascript: The good parts .


3
Rzeczywiście bardzo ładne, ale tak naprawdę nie możesz wywołać base_dofunkcji, ponieważ w ten sposób tracisz jakiekolwiek thispowiązanie w oryginalnej dometodzie. Tak więc konfiguracja metody podstawowej jest nieco bardziej złożona, zwłaszcza jeśli chcesz ją wywołać za pomocą obiektu podstawowego thiszamiast obiektu podrzędnego. Sugerowałbym coś podobnego base_do.apply(me, arguments).
Giulio Piancastelli

Zastanawiam się tylko, dlaczego nie używasz varfor base_do? Czy jest to celowe, a jeśli tak, to dlaczego? I, jak powiedział @GiulioPiancastelli, czy to zrywa thiswiązanie wewnątrz, base_do()czy to wywołanie odziedziczy po thisdzwoniącym?
binki

@binki Zaktualizowałem przykład. varPowinno tam być. Użycie call(lub apply) umożliwia nam thisprawidłowe wiązanie .
Magnar

10

Wiem, że ten post jest sprzed 4 lat, ale ze względu na moje tło w języku C # szukałem sposobu na wywołanie klasy bazowej bez konieczności określania nazwy klasy, a raczej uzyskanie jej przez właściwość w podklasie. Więc jedyną moją zmianą w odpowiedzi Christopha byłaby

Od tego:

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

Do tego:

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

6
Nie rób tego, this.constructormoże nie zawsze wskazywać MyClass.
Bergi

Cóż, powinno, chyba że ktoś schrzanił ustalenie spadku. „this” powinno zawsze odnosić się do obiektu najwyższego poziomu, a jego właściwość „constructor” powinna zawsze wskazywać na jego własną funkcję konstruktora.
Triynko

5

jeśli zdefiniujesz taką funkcję (używając OOP)

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

istnieją dwa sposoby wywołania funkcji prototypowej: 1) utwórz instancję i wywołaj funkcję obiektu:

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

a drugi sposób to ... 2) wywołuje funkcję bezpośrednio z prototypu:

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

5

To rozwiązanie wykorzystuje Object.getPrototypeOf

TestA jest super, że ma getName

TestBjest dzieckiem, które zastępuje, getNameale również ma, getBothNamesktóre wywołuje zarówno superwersję, getNamejak i childwersję

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

Alternatywa :

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

Mimo że może działać, nie polecam tego rozwiązania. Jest wiele powodów, dla __proto__których należy unikać używania (które zmienia [[Prototype]] `obiektu). Object.create(...)Zamiast tego skorzystaj z trasy.
KarelG

2

Jeśli dobrze rozumiem, zależy Ci na tym, aby funkcjonalność Base była zawsze wykonywana, a część pozostawić wdrożeniom.

Możesz uzyskać pomoc dzięki wzorcowi projektowemu „ metoda szablonu ”.

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

Jeśli znasz swoją superklasę z nazwy, możesz zrobić coś takiego:

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

To się wydrukuje

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

zgodnie z oczekiwaniami.


1

Innym sposobem z ES5 jest jawne przejście przez łańcuch prototypów przy użyciu 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

Nie, musiałbyś nadać funkcji do w konstruktorze i funkcji do w prototypie różnych nazw.


0

Ponadto, jeśli chcesz zastąpić wszystkie wystąpienia, a nie tylko jedno specjalne wystąpienie, ten może pomóc.

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

wynik:

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();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.