Metody nadpisywania kodu JavaScript

87

Powiedzmy, że masz poniższy kod:

function A() {
    function modify() {
       x = 300;
       y = 400;
    }

    var c = new C();
}

function B() {
    function modify(){
       x = 3000;
       y = 4000;
    }

    var c = new C();
}

C = function () {
   var x = 10;
   var y = 20;

   function modify() {
      x = 30;
      y = 40;
   };

   modify();
   alert("The sum is: " + (x+y));
}

Nasuwa się pytanie, czy istnieje jakikolwiek sposób, w jaki mogę zastąpić metodę modifyod Cz metodami, które są w Ai B. W Javie superużyłbyś -klucza kluczowego, ale jak możesz osiągnąć coś takiego w JavaScript?

Daniel Nastase
źródło
6
modifynie jest metodą, ale funkcją zagnieżdżoną - jest różnica między tymi dwoma ...
Šime Vidas
2
W Javie używasz supersłowa kluczowego, aby uzyskać dostęp do nieprywatnych pól i metod superklasy. Nie używasz go do ich zastępowania.
FK82

Odpowiedzi:

138

Edycja: minęło już sześć lat od napisania oryginalnej odpowiedzi i wiele się zmieniło!

  • Jeśli używasz nowszej wersji JavaScript, prawdopodobnie skompilowanej za pomocą narzędzia takiego jak Babel , możesz użyć prawdziwych klas .
  • Jeśli używasz klasowych konstruktorów komponentów dostarczonych przez Angular lub React , powinieneś zajrzeć do dokumentacji dla tego frameworka.
  • Jeśli używasz ES5 i tworzysz „fałszywe” klasy ręcznie przy użyciu prototypów, odpowiedź poniżej jest nadal tak dobra, jak zawsze.

Powodzenia!


Dziedziczenie JavaScript wygląda nieco inaczej niż Java. Oto jak wygląda natywny system obiektów JavaScript:

// Create a class
function Vehicle(color){
  this.color = color;
}

// Add an instance method
Vehicle.prototype.go = function(){
  return "Underway in " + this.color;
}

// Add a second class
function Car(color){
  this.color = color;
}

// And declare it is a subclass of the first
Car.prototype = new Vehicle();

// Override the instance method
Car.prototype.go = function(){
  return Vehicle.prototype.go.call(this) + " car"
}

// Create some instances and see the overridden behavior.
var v = new Vehicle("blue");
v.go() // "Underway in blue"

var c = new Car("red");
c.go() // "Underway in red car"

Niestety jest to trochę brzydkie i nie zawiera zbyt przyjemnego sposobu na "super": musisz ręcznie określić metodę klas nadrzędnych, którą chcesz wywołać. W rezultacie istnieje wiele narzędzi ułatwiających tworzenie zajęć. Spróbuj spojrzeć na Prototype.js, Backbone.js lub podobną bibliotekę, która zawiera ładniejszą składnię do wykonywania OOP w js.

Adam
źródło
2
Alternatywnie, aby użyć narzędzia do "uprzyjemniania tworzenia klas", nie mógłbyś w ogóle tworzyć klas. Klasyczna emulacja OO w js zawsze powoduje bałagan.
Raynos,
1
(komentarz nie konstruktywny) za bycie językiem „niskiego poziomu” w świecie przeglądarek jest bardzo brzydki bez powodu. Wciąż się tego uczę, dzięki!
dnuske
9
Uważam, że zamiast Car.prototype = nowy pojazd (); powinno to być Car.prototype = Object.create (Vehicle.prototype); Nie?
Jordan,
@Martin ma rację, zobacz javascript-
inheritance
Jeden z powodów, dla których Car.prototype = new Vehicle (); jest niepoprawny, ponieważ nie ma nic do przekazania do pojazdu (), gdy otrzymuje kolor.
Tushar Arora
63

Ponieważ jest to najpopularniejszy hit w Google, chciałbym udzielić zaktualizowanej odpowiedzi.

Używanie klas ES6 znacznie ułatwia dziedziczenie i zastępowanie metod:

'use strict';

class A {
    speak() {
        console.log("I'm A");
    }
}

class B extends A {
    speak() {
        super.speak();

        console.log("I'm B");
    }
}

var a = new A();
a.speak();
// Output:
// I'm A

var b = new B();
b.speak();
// Output:
// I'm A
// I'm B

Słowo superkluczowe odnosi się do klasy nadrzędnej, gdy jest używane w klasie dziedziczącej. Ponadto wszystkie metody w klasie nadrzędnej są powiązane z instancją dziecka, więc nie musisz pisać super.method.apply(this);.

Jeśli chodzi o kompatybilność: tabela kompatybilności ES6 pokazuje tylko najnowsze wersje klas wsparcia głównych graczy (głównie). Przeglądarki V8 mają je od stycznia tego roku (Chrome i Opera), a Firefox, korzystający z silnika SpiderMonkey JS, doczeka się zajęć w przyszłym miesiącu z oficjalnym wydaniem Firefoksa 45. Po stronie mobilnej Android nadal nie obsługuje tej funkcji, podczas gdy iOS 9, wydany pięć miesięcy temu, ma częściową obsługę.

Na szczęście istnieje Babel , biblioteka JS do ponownej kompilacji kodu Harmony do kodu ES5. Klasy i wiele innych fajnych funkcji w ES6 może sprawić, że kod Javascript będzie dużo bardziej czytelny i łatwiejszy w utrzymaniu.

Yotam Ofek
źródło
10
Ta odpowiedź zasługuje na więcej uznania i jest obecnie właściwą odpowiedzią.
Klompenrunner,
6

Kiedyś należy unikać emulacji klasycznego OO i zamiast tego używać prototypowego OO. Ładną biblioteką narzędzi dla prototypowych obiektów obiektowych są cechy .

Zamiast nadpisywać metody i konfigurować łańcuchy dziedziczenia (zawsze należy przedkładać kompozycję obiektu nad dziedziczenie obiektów), powinieneś łączyć funkcje wielokrotnego użytku w cechy i tworzyć z nimi obiekty.

Przykład na żywo

var modifyA = {
    modify: function() {
        this.x = 300;
        this.y = 400;
    }
};

var modifyB = {
    modify: function() {
        this.x = 3000;
        this.y = 4000;
    }
};

C = function(trait) {
    var o = Object.create(Object.prototype, Trait(trait));

    o.modify();
    console.log("sum : " + (o.x + o.y));

    return o;
}

//C(modifyA);
C(modifyB);
Raynos
źródło
10
Nie odpowiadasz na pytanie. To powinien być komentarz, jeśli cokolwiek.
FK82
3

modified () w twoim przykładzie to funkcja prywatna, która nie będzie dostępna z dowolnego miejsca poza definicją A, B lub C. Musisz to zadeklarować jako

this.modify = function(){}

C nie ma odniesienia do swoich rodziców, chyba że przekażesz je do C. Jeśli C jest skonfigurowane do dziedziczenia z A lub B, odziedziczy swoje metody publiczne (a nie swoje funkcje prywatne, takie jak zdefiniowano modyfikację ()). Gdy C odziedziczy metody od swojego rodzica, możesz zastąpić odziedziczone metody.

Alex Heyd
źródło
modyfikacja jest funkcją lokalną. Nie ma prywatnego w javascript
Raynos
lokalny / prywatny, czy to nie to samo, tylko inny termin?
Alex Heyd
2

metoda modify(), którą wywołałeś w poprzednim, jest wywoływana w kontekście globalnym, jeśli chcesz nadpisać, modify()musisz najpierw dziedziczyć Alub B.

Może próbujesz to zrobić:

W tym przypadku CdziedziczyA

function A() {
    this.modify = function() {
        alert("in A");
    }
}

function B() {
    this.modify = function() {
        alert("in B");
    }
}

C = function() {
    this.modify = function() {
        alert("in C");
    };

    C.prototype.modify(); // you can call this method where you need to call modify of the parent class
}

C.prototype = new A();
miłość
źródło
1
C.prototype.modify()miałby niewłaściwą thiswartość. Mianowicie C.prototypezamiast przypadku c. Proszę użyć, .call(this)ale wtedy twoja odpowiedź jest tylko duplikatem :)
Raynos
1

Nie, chyba że uczynisz wszystkie zmienne „publicznymi”, tj. Uczynisz je członkami Functionalbo bezpośrednio, albo przez prototypewłaściwość.

var C = function( ) {
    this.x = 10 , this.y = 20 ;
    this.modify = function( ) {
        this.x = 30 , this.y = 40 ;
        console.log("(!) C >> " + (this.x + this.y) ) ;
    } ;
} ;

var A = function( ) {
    this.modify = function( ) {
       this.x = 300 , this.y = 400 ;
       console.log("(!) A >> " + (this.x + this.y) ) ;
    } ;
} ;
    A.prototype = new C ;

var B = function( ) {
    this.modify = function( ) {
       this.x = 3000 , this.y = 4000 ;
       console.log("(!) B >> " + (this.x + this.y) ) ;
    } ;
} ;


new C( ).modify( ) ;
new A( ).modify( ) ;
new B( ).modify( ) ; 

Zauważysz kilka zmian.

Co najważniejsze, wywołanie rzekomego konstruktora „superklas” jest teraz niejawne w tym wierszu:

<name>.prototype = new C ;

Zarówno Ai Bbędzie teraz mieć indywidualnie modyfikowalnych członków xi yktóre nie byłoby w przypadku, gdyby pisaliśmy ... = Czamiast.

Następnie x, yi modifywszystkie są „publiczne” Członkowie tak że przypisywanie innym Functiondo nich

 <name>.prototype.modify = function( ) { /* ... */ }

zastąpi oryginał Functiontą nazwą.

Wreszcie, wywołanie do modifynie może być wykonane w Functiondeklaracji, ponieważ niejawne wywołanie „superklasy” byłoby następnie wykonane ponownie, gdy przypuszczaliśmy „superklasę” na prototypewłasność rzekomej „podklasy”.

Ale cóż, tak mniej więcej można to zrobić w JavaScript.

HTH,

FK

FK82
źródło
Nie ma sensu w <name>.prototype = new C;swoim shadowing wszystkich członków prototypu w każdym razie
Raynos
@Raynos: Tak, jest. To znaczy, całe dziedziczenie Objectsbędzie współużytkować tego samego członka, Cjeśli nie utworzysz wystąpienia Cobiektu. W ten sposób zmienia xsię Azmieni xw C, a tym samym zmiany xw B. Co jest oczywiście niepożądane.
FK82
przegapiłeś punkt. Możesz usunąć linię, a kod nadal będzie działał
Raynos
@Raynos: Obawiam się, że brakuje ci własnego punktu widzenia. ;-) Chcemy Ai Bdziedziczymy po C. Gdyby brakowało tej linii, nie byłoby to prawdą. W rzeczywistości jedynym prototypem, do którego w takim przypadku miałby dostęp zarówno „cień”, Ajak i B„cień” byłby Object.prototype.
FK82
spójrz na kod. A i B nie używają żadnego z członków C do prototypu. Zatem „dziedziczenie” z C jest bezużyteczne. To dlatego, że A i B definiuj x, ya modify, a tym samym cień wszystkich Cczłonków tych. Jaki jest sens umieszczania C w prototypie, jeśli go nie używasz? To martwy kod.
Raynos,
0

function A() {
    var c = new C();
	c.modify = function(){
		c.x = 123;
		c.y = 333;
	}
	c.sum();
}

function B() {
    var c = new C();
	c.modify = function(){
		c.x = 999;
		c.y = 333;
	}
	c.sum();
}


C = function () {
   this.x = 10;
   this.y = 20;

   this.modify = function() {
      this.x = 30;
      this.y = 40;
   };
   
   this.sum = function(){
	this.modify();
	console.log("The sum is: " + (this.x+this.y));
   }
}

A();
B();

nghien_net 89
źródło