2010-09-01 14 views
20

Myślę, że źle zrozumiałem, jak działa prototypowe dziedzictwo Javascript. W szczególności, wewnętrzne zmienne prototypów wydają się być współdzielone między wieloma różnymi pod-obiektami. Najłatwiej jest zilustrowanie z kodem:Zmienne prywatne w odziedziczonych prototypach

var A = function() 
{ 
    var internal = 0; 
    this.increment = function() 
    { 
    return ++internal; 
    }; 
}; 

var B = function() {}; 
// inherit from A 
B.prototype = new A; 

x = new B; 
y = new B; 

$('#hello').text(x.increment() + " - " + y.increment());​ 

ten wyprowadza 1 - 2 (przetestować go na JSBin), a ja w pełni spodziewać, że wynik będzie 1 - 1, ponieważ chciałem dwa oddzielne obiekty.

Jak mogę się upewnić, że obiekt A nie jest obiektem współdzielonym między wieloma instancjami B?

Aktualizacja: This article podkreśla niektóre z zagadnień:

Problem polega na tym, że zakres każde podejście wykorzystuje do tworzenia zmiennej prywatnej, która działa dobrze, jest również zamknięcie, w działaniu, że wyniki w przypadku zmiany zmiennej prywatnej dla jednej instancji obiektu, jest ona zmieniana dla wszystkich. To znaczy. to bardziej przypomina prywatną własność statyczną niż rzeczywistą zmienną prywatną.

Tak więc, jeśli chcesz mieć coś prywatnego, bardziej niepubliczną stałą, każde z powyższych podejść jest dobre, ale nie dla rzeczywistych zmiennych prywatnych. Prywatne zmienne działają bardzo dobrze tylko z obiektami singleton w JavaScript.

Rozwiązanie: Jak na odpowiedź BGerrissen użytkownika, zmieniając Deklaracji B i pozostawiając prototypu działa zgodnie z przeznaczeniem:

var B = function() { A.apply(this, arguments); }; 

Odpowiedz

17

Prywatni członkowie są podchwytliwe przy użyciu prototypowego dziedziczenia. Po pierwsze, nie mogą być dziedziczone. Musisz utworzyć prywatnych członków w każdym konstruktorze. Możesz to zrobić przez zastosowanie super-konstruktora w podklasie lub stworzenie dekoratora.

Dekorator przykład:

function internalDecorator(obj){ 
    var internal = 0; 
    obj.increment = function(){ 
     return ++internal; 
    } 
} 

var A = function(){ 
    internalDecorator(this); 
} 
A.prototype = {public:function(){/*etc*/}} 

var B = function(){ 
    internalDecorator(this); 
} 
B.prototype = new A(); // inherits 'public' but ALSO redundant private member code. 

var a = new B(); // has it's own private members 
var b = new B(); // has it's own private members 

To jest po prostu odmianą super wywołanie konstruktora, można również osiągnąć ten sam dzwoniąc rzeczywistą super-konstruktora z .apply()

var B = function(){ 
    A.apply(this, arguments); 
} 

Teraz stosując dziedziczenie poprzez B.prototype = new A() wywołujesz niepotrzebny kod konstruktora z A.Sposobem na uniknięcie tego jest użycie Douglas Crockfords spłodzić metody:

Object.beget = function(obj){ 
    var fn = function(){} 
    fn.prototype = obj; 
    return new fn(); // now only its prototype is cloned. 
} 

Które użyć w następujący sposób:

B.prototype = Object.beget(A.prototype); 

Oczywiście, można zrezygnować z dziedziczenia całkowicie i zrobić dobry użytek z dekoratorów, przynajmniej gdzie potrzebni są prywatni członkowie.

5

Punktem prototypu jest to, że jest on dzielony pomiędzy wielu obiektów (tj. te, które są tworzone przez tę samą funkcję konstruktora). Jeśli potrzebujesz zmiennych, które nie są współdzielone między obiektami, które mają wspólny prototyp, musisz zachować te zmienne w funkcji konstruktora dla każdego obiektu. Po prostu użyj prototypu do dzielenia się metodami.

W twoim przykładzie nie możesz zrobić dokładnie tego, czego chcesz, używając prototypów. Oto co możesz zrobić zamiast tego. Zobacz odpowiedź Daniela Earwicker'a, aby uzyskać więcej wyjaśnień, że nie ma sensu powtarzać tutaj.

var A = function() {}; 

A.prototype.incrementPublic = function() 
{ 
    return ++this.publicProperty; 
}; 

var B = function() 
{ 
    this.publicProperty = 0; 
    var internal = 0; 
    this.incrementInternal = function() 
    { 
     return ++internal; 
    }; 
}; 

B.prototype = new A(); 

var x = new B(), y = new B(); 
console.log(x.incrementPublic(), y.incrementPublic()); // 1, 1 
console.log(x.incrementInternal(), y.incrementInternal()); // 1, 1 
+1

Ale cały Chodzi o to, że funkcjonalność należy w 'A' i jest dzielone między różnymi pod-„klas”(', 'C' B' i 'D'). Czy nie ma możliwości dla niestacjonarnych zmiennych prywatnych w nadklasach w JavaScript? –

+1

Powszechnym sposobem oznaczania intencji "prywatnych" (czyli ostrzegania programistów, że coś specjalnego dzieje się z właściwością) jest użycie podkreślenia jako przedrostka nazwy właściwości (np. Obj._privateMember). Chociaż będzie to działało z dziedziczeniem łańcuchów i liczb, zaczyna być bardziej złożone, gdy używamy obiektów i tablic jako właściwości. – BGerrissen

+0

Nie jestem dokładnie tego, o co pytasz. Czy chcesz, aby zmienna zdefiniowana w funkcji 'A' była dostępna dla metod' B', 'C' i' D', ale nigdzie indziej? Staraj się nie myśleć o rzeczach w JavaScript pod względem cech innych języków (takich jak Java czy C#). JavaScript po prostu nie ma klas. Posiada obiekty dziedziczące właściwości z innych obiektów za pośrednictwem łańcucha prototypów. –

15

Musisz zapomnieć o idei zajęć. W JavaScript nie ma czegoś takiego jak "instancja B". Jest tylko "jakiś obiekt uzyskany przez wywołanie funkcji konstruktora B '. Obiekt ma właściwości. Niektóre są jego "własnymi" właściwościami, inne są uwzględniane podczas przeszukiwania łańcucha prototypów.

Kiedy mówisz new A, tworzysz jeden obiekt. Następnie przypisujesz go jako prototyp dla B, co oznacza, że ​​każde wywołanie do new B tworzy nowy obiekt, który ma ten sam bezpośredni prototyp, a więc tę samą zmienną licznika.

W odpowiedzi udzielonej przez firmę Tim Down wyświetlane są dwie alternatywy. Jego incrementPublic używa dziedziczenia, ale powoduje, że zmienna licznika jest publiczna (tj. Rezygnuje z enkapsulacji). Podczas gdy incrementInternal powoduje, że licznik jest prywatny, ale osiąga sukces poprzez przeniesienie kodu do B (tj. Porzuca dziedziczenie).

Co chcesz to połączenie trzech rzeczy:

  • dziedziczone zachowanie - tak to musi być zdefiniowana w A i nie wymagają kodu w B oprócz ustawiania prototyp
  • prywatne dane, przechowywane w zamknięciu -local zmienne
  • dane o wystąpieniu, przechowywane w this.

Problem stanowi sprzeczność między dwoma ostatnimi. Powiedziałbym również, że dziedziczenie ma i tak ograniczoną wartość w JS. Lepiej traktować go jako języka funkcjonalnego:

// higher-order function, returns another function with counter state 
var makeCounter = function() { 
    var c = 0; 
    return function() { return ++c; }; 
}; 

// make an object with an 'increment' method: 
var incrementable = { 
    increment: makeCounter() 
}; 

Osobiście unikają funkcji konstruktora prototyp dziedziczenie przez większość czasu. Są znacznie mniej użyteczne w JS, niż zakładają ludzie z OO.

Aktualizacja Byłbym ostrożny z oświadczeniem pan cytowany w aktualizacji:

zmienne prywatne działają tylko naprawdę dobrze z pojedynczych obiektów w JavaScript.

To nie prawda. Obiekt jest po prostu "słownikiem" właściwości, z których każda może być funkcją. Zapomnij o przedmiotach i pomyśl o funkcjach. Można utworzyć wiele instancji funkcji według pewnego wzorca, pisząc funkcję zwracającą funkcję. Przykład ten jest tylko prostym przykładem. makeCounter nie jest "obiektem singleton" i nie musi być ograniczone do użycia w obiektach singleton.

+1

+1. Dobre wytłumaczenie. Zostawię odpowiedź tak, jak jest, i nie próbuję przepisywać tego, co tu napisałeś. Osobiście uważam, że prototypy i konstruktory są przydatne, choćby tylko w celu dzielenia się metodami między wieloma powiązanymi obiektami. –

+0

+1 za oświecający prototypowy charakter. – BGerrissen

0

Właśnie znalazłem inne podchwytliwe rozwiązanie, bez eksportowania jakichkolwiek metod/zmiennych do obiektów publicznych.

function A(inherit) { 
    var privates = { //setup private vars, they could be also changed, added in method or children 
     a: 1, 
     b: 2, 
     c: 3 
    }; 
    //setup public methods which uses privates 
    this.aPlus = bindPlus("aPlus", this, privates); //pass method name as string! 
    this.aGet = bindPlus("aGet", this, privates); 
    if (inherit) { 
     return privates; 
    } 
} 
A.prototype.aPlus = function() { 
    var args = getArgs(arguments), 
     //self is "this" here 
     self = args.shift(), 
     privates = args.shift(), 
     //function real arguments 
     n = args.shift(); 
    return privates.a += n; 
}; 

A.prototype.aGet = function (n) { 
    var args = getArgs(arguments), 
     self = args.shift(), 
     privates = args.shift(); 
    console.log(this, self, privates); 
    return privates.a; 
}; 

//utilites 
function getArgs(arg) { 
    return Array.prototype.slice.call(arg); 
} 

function bindPlus(funct, self, privates) { 
    return function() { 
     return Object.getPrototypeOf(self)[funct].bind(this, self, privates).apply(null, arguments); 
    }; 
} 

//inherited 
function B(inherit) { 
    var privates = Object.getPrototypeOf(this).constructor.call(this, true); 
    privates.d = 4; 
    this.dGet = bindPlus("dGet", this, privates); 
    if (inherit) { 
     return privates; 
    } 
} 

B.prototype = Object.create(A.prototype); 
B.constructor = B; 

B.prototype.aGet = function() { 
    var args = getArgs(arguments), 
     self = args.shift(), 
     privates = args.shift(); 
    console.warn("B.aGet", this, privates); 
    return privates.a; 
}; 

B.prototype.dGet = function() { 
    var args = getArgs(arguments), 
     self = args.shift(), 
     privates = args.shift(); 
    console.warn("B.dGet", this, privates); 
    return privates.d; 
}; 


// tests 
var b = new B(); 
var a = new A(); 

//should be 223 
console.log("223 ?",b.aPlus(222)); 

//should be 42 
console.log("41",a.aPlus(222)); 

Więcej testu oraz próbki tutaj: http://jsfiddle.net/oceog/TJH9Q/

Powiązane problemy