2011-11-11 13 views
35

Rozważ następujący kod.Dziedziczenie JavaScript i własność konstruktora

function a() {} 
function b() {} 
function c() {} 

b.prototype = new a(); 
c.prototype = new b(); 

console.log((new a()).constructor); //a() 
console.log((new b()).constructor); //a() 
console.log((new c()).constructor); //a() 
  • Dlaczego nie jest konstruktor aktualizowana bic?
  • Czy robię dziedziczenie źle?
  • Jaki jest najlepszy sposób aktualizacji konstruktora?

Ponadto rozważ następujące kwestie.

console.log(new a() instanceof a); //true 
console.log(new b() instanceof b); //true 
console.log(new c() instanceof c); //true 
  • Zważywszy, że (new c()).constructor jest równa a() i Object.getPrototypeOf(new c()) jest a{ }, jak to możliwe, instanceof wiedzieć, że new c() jest instancją c?

http://jsfiddle.net/ezZr5/

+3

Każdy powód, dla którego potrzebujesz aktualizacji konstruktora? Uważam, że moje życie jest łatwiejsze, gdybym tylko udawał, że własność nie istnieje. – hugomg

+0

Trudno jest zamknąć to jako duplikat - wszystkie inne questinos są tak szczegółowe ... – hugomg

+3

'c.prototype' to' b() 'i' b.prototype' to 'a()', dlatego 'c.prototype' to' a() ' –

Odpowiedz

63

Dobra, zagrajmy trochę grę pamiętać:

z powyższego obrazka widzimy:

  1. Kiedy utworzyć funkcję jak function Foo() {}, JavaScript tworzy instancję Function.
  2. Każda instancja Function (funkcja konstruktora) ma właściwość prototype, która jest wskaźnikiem.
  3. Właściwość prototype wskazuje na obiekt prototypowy.
  4. Obiekt prototypowy ma właściwość constructor, która jest również wskaźnikiem.
  5. Właściwość obiektu prototypowego wskazuje na jego funkcję konstruktora.
  6. Kiedy tworzymy nową instancję Foo, taką jak new Foo(), JavaScript tworzy nowy obiekt.
  7. Wewnętrzna właściwość [[proto]] wskazuje na prototyp konstruktora.

Teraz powstaje pytanie, dlaczego JavaScript nie dołącza do obiektu instancji obiektu zamiast prototypu. Rozważmy:

function defclass(prototype) { 
    var constructor = prototype.constructor; 
    constructor.prototype = prototype; 
    return constructor; 
} 

var Square = defclass({ 
    constructor: function (side) { 
     this.side = side; 
    }, 
    area: function() { 
     return this.side * this.side; 
    } 
}); 

var square = new Square(10); 

alert(square.area()); // 100 

Jak widać własnością constructor jest po prostu inna metoda prototypu, jak area w powyższym przykładzie. Cechą wyróżniającą właściwość constructor jest jej inicjalizacja wystąpienia prototypu. W przeciwnym razie jest dokładnie taka sama, jak każda inna metoda prototypu.

Definiowanie właściwości constructor na prototypie jest korzystne z następujących powodów:

  1. To logicznie poprawne. Na przykład rozważ Object.prototype. Właściwość constructor z Object.prototype wskazuje na Object. Jeśli właściwość constructor została zdefiniowana w instancji, wówczas Object.prototype.constructor będzie undefined, ponieważ Object.prototype jest instancją null.
  2. To nie jest traktowane inaczej niż inne prototypowe metody. To sprawia, że ​​zadanie new jest łatwiejsze, ponieważ nie musi definiować właściwości dla każdej instancji.
  3. Każda instancja ma tę samą właściwość constructor. Dlatego jest wydajny.

Teraz, gdy mówimy o dziedziczeniu, mamy następujący scenariusz:

z powyższego obrazka widzimy:

  1. konstruktora potomnej prototype właściwość jest ustawiona na wystąpienie podstawowego konstruktora.
  2. W związku z tym wewnętrzna właściwość [[proto]] instancji konstruktora pochodnego wskazuje na to również.
  3. Tym samym właściwość constructor wyjściowej instancji konstruktora wskazuje teraz na konstruktor bazowy.

Co się tyczy operatora instanceof, w przeciwieństwie do powszechnego przekonania, nie jest zależne od właściwości instancji danej constructor. Jak widać z góry, doprowadziłoby to do błędnych wyników.

Operator instanceof jest operatorem binarnym (ma dwa operandy). Działa na obiekcie instancji i funkcji konstruktora. Jak wytłumaczyć na Mozilla Developer Network, po prostu wykonuje następujące operacje:

function instanceOf(object, constructor) { 
    while (object != null) { 
     if (object == constructor.prototype) { //object is instanceof constructor 
      return true; 
     } else if (typeof object == 'xml') { //workaround for XML objects 
      return constructor.prototype == XML.prototype; 
     } 
     object = object.__proto__; //traverse the prototype chain 
    } 
    return false; //object is not instanceof constructor 
} 

Mówiąc prościej, jeśli Foo dziedziczy Bar, wówczas łańcuch prototypem instancji Foo byłoby:

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

Jak widać, każdy obiekt dziedziczy po konstruktorze Object. Prototypowy łańcuch kończy się, gdy wewnętrzna właściwość [[proto]] wskazuje na null.

Funkcja instanceof prostu przechodzi łańcuch prototyp obiektu przykład (pierwszy operand) i porównuje się z wewnętrzną [[proto]] właściwość każdego przedmiotu, który ma właściwości prototype funkcji konstruktor (drugiego argumentu). Jeśli pasują, zwraca true; w przeciwnym razie, jeśli łańcuch prototypów się kończy, zwraca false.

+4

zobacz także http://joost.zeekat.nl/constructors-considered-mily-confusing.html – Christoph

+3

+1 Wolałbym jednak 'Object.getPrototypeOf' zamiast' .__ proto__'. – pimvdb

+3

Użyłem tylko własności '__proto__' dla wyjaśnienia. Tak to zostało wyjaśnione w Mozilla Developer Network. Jednak właściwość '__proto__' ma przewagę nad metodą' Object.getPrototypeOf' niż jest szybsza (brak nagłówków wywołania funkcji) i jest implementowana przez wszystkie główne przeglądarki. Jedynym powodem, dla którego użyłbym 'Object.getPrototypeOf' jest praca z implementacjami takimi jak Rhino, które nie obsługują właściwości' __proto__'. Wszyscy mamy własne preferencje. Wolę właściwość '__proto__', ponieważ jest bardziej czytelna. Twoje zdrowie. =) –

12

Domyślnie

function b() {} 

następnie b.prototype ma .constructor właściwość, która jest automatycznie ustawiony na b. Jednak aktualnie nadpisywania prototyp, a tym samym odrzucając tej zmiennej:

b.prototype = new a; 

Następnie b.prototype nie mają właściwość .constructor już; został wymazany z nadpisaniem. To ma jednak dziedziczy po a jednak i (new a).constructor === a, a zatem (new b).constructor === a (odnosi się do tej samej właściwości w łańcuchu prototypów).

Najlepiej zrobić to po prostu ustawienie go ręcznie później:

b.prototype.constructor = b; 

Można również zrobić trochę funkcję to:

function inherit(what, from) { 
    what.prototype = new from; 
    what.prototype.constructor = what; 
} 

http://jsfiddle.net/79xTg/5/

+1

To wszystko prawda. Ale wtedy nie wiedziałbyś, że pewien przedmiot jest * dziedziczony *. 'new b() instanceof a' zwraca' false' przy użyciu '$ .extend', który może być niepożądany w przypadku OP, jeśli musi sprawdzić dziedziczenie. –

+0

@Robert Koritnik: Dobrze, ale 'b instanceof b' jest zawsze fałszywe, ponieważ konstruktor nie jest instancją samego siebie. Funkcja ext wydaje się działać dla 'nowej instancji b b ', chyba że cię źle zrozumiałem: http://jsfiddle.net/79xTg/3/. – pimvdb

+0

Poprawiłem mój komentarz. To był oczywiście literówka. I pozwól mi edytować twój przykład: http://jsfiddle.net/79xTg/4/ '$ .extend' nie dziedziczy, ale raczej' prototype' copy. Dlatego nie można sprawdzić dziedziczenia, używając go. –

4

constructor jest regularny, nie -odliczalna właściwość domyślnej wartości właściwości obiektów funkcji. Tym samym przypisanie do prototype spowoduje utratę tej właściwości.

instanceof będzie nadal działać, ponieważ nie używa constructor, lecz skanuje łańcuch prototypów obiektu dla (bieżącego) wartość funkcji w prototype nieruchomości, tj foo instanceof Foo jest równoważna

var proto = Object.getPrototypeOf(foo); 
for(; proto !== null; proto = Object.getPrototypeOf(proto)) { 
    if(proto === Foo.prototype) 
     return true; 
} 
return false; 

W ECMAScript3 , nie ma sposobu, aby ustawić właściwość, która zachowuje się identycznie jak wbudowana, ponieważ właściwości zdefiniowane przez użytkownika są zawsze przeliczalne (tj. widoczne dla for..in).

Zmiana za pomocą ECMAScript5. Jednak nawet jeśli ręcznie ustawisz constructor, Twój kod nadal będzie miał problemy: W szczególności, niewłaściwe jest ustawienie prototype na instancję klasy "rodzic" - nie należy wywoływać konstruktora nadrzędnego, gdy klasa potomna-' "jest zdefiniowany, ale raczej gdy tworzone są instancje podrzędne.

Oto niektóre ECMAScript5 kod przykładem, jak należy to zrobić:

function Pet(name) { 
    this.name = name; 
} 

Pet.prototype.feed = function(food) { 
    return this.name + ' ate ' + food + '.'; 
}; 

function Cat(name) { 
    Pet.call(this, name); 
} 

Cat.prototype = Object.create(Pet.prototype, { 
    constructor : { 
     value : Cat, 
     writable : true, 
     enumerable : false, 
     configurable : true 
    } 
}); 

Cat.prototype.caress = function() { 
    return this.name + ' purrs.'; 
}; 

Jeśli utkniesz z ECMAScript3, musisz użyć niestandardowego clone() function zamiast Object.create() i nie będzie mógł dokonać constructor non-przeliczalny:

Cat.prototype = clone(Pet.prototype); 
Cat.prototype.constructor = Cat; 
Powiązane problemy