Programuję w językach OOP od ponad 10 lat, ale teraz uczę się JavaScript i pierwszy raz zetknąłem się z dziedziczeniem opartym na prototypach. Uczę się najszybciej, ucząc się dobrego kodu. Jaki jest dobrze napisany przykład aplikacji (lub biblioteki) JavaScript, która prawidłowo wykorzystuje dziedziczenie prototypowe? Czy możesz opisać (krótko), jak / gdzie używane jest dziedziczenie prototypowe, abym wiedział, od czego zacząć czytać?
javascript
prototypal-inheritance
Alex Reisner
źródło
źródło
Odpowiedzi:
Douglas Crockford ma fajną stronę poświęconą dziedziczeniu prototypów JavaScript :
Prace Dean Edward's Base.js , Mootools's Class lub John Resig's Simple Inheritance to sposoby na klasyczne dziedziczenie w JavaScript.
źródło
newObj = Object.create(oldObj);
jeśli chcesz, aby był wolny od zajęć? W przeciwnym razie zamieńoldObj
na obiekt prototypowy funkcji konstruktora, która powinna działać?Jak już wspomniano, filmy Douglasa Crockforda dobrze wyjaśniają, dlaczego i jak. Ale żeby umieścić to w kilku wierszach JavaScript:
// Declaring our Animal object var Animal = function () { this.name = 'unknown'; this.getName = function () { return this.name; } return this; }; // Declaring our Dog object var Dog = function () { // A private variable here var private = 42; // overriding the name this.name = "Bello"; // Implementing ".bark()" this.bark = function () { return 'MEOW'; } return this; }; // Dog extends animal Dog.prototype = new Animal(); // -- Done declaring -- // Creating an instance of Dog. var dog = new Dog(); // Proving our case console.log( "Is dog an instance of Dog? ", dog instanceof Dog, "\n", "Is dog an instance of Animal? ", dog instanceof Animal, "\n", dog.bark() +"\n", // Should be: "MEOW" dog.getName() +"\n", // Should be: "Bello" dog.private +"\n" // Should be: 'undefined' );
Problem z tym podejściem polega jednak na tym, że będzie on odtwarzał obiekt za każdym razem, gdy go utworzysz. Innym podejściem jest zadeklarowanie obiektów na stosie prototypów, na przykład:
// Defining test one, prototypal var testOne = function () {}; testOne.prototype = (function () { var me = {}, privateVariable = 42; me.someMethod = function () { return privateVariable; }; me.publicVariable = "foo bar"; me.anotherMethod = function () { return this.publicVariable; }; return me; }()); // Defining test two, function var testTwo = function() { var me = {}, privateVariable = 42; me.someMethod = function () { return privateVariable; }; me.publicVariable = "foo bar"; me.anotherMethod = function () { return this.publicVariable; }; return me; }; // Proving that both techniques are functionally identical var resultTestOne = new testOne(), resultTestTwo = new testTwo(); console.log( resultTestOne.someMethod(), // Should print 42 resultTestOne.publicVariable // Should print "foo bar" ); console.log( resultTestTwo.someMethod(), // Should print 42 resultTestTwo.publicVariable // Should print "foo bar" ); // Performance benchmark start var stop, start, loopCount = 1000000; // Running testOne start = (new Date()).getTime(); for (var i = loopCount; i>0; i--) { new testOne(); } stop = (new Date()).getTime(); console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds'); // Running testTwo start = (new Date()).getTime(); for (var i = loopCount; i>0; i--) { new testTwo(); } stop = (new Date()).getTime(); console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');
Jeśli chodzi o introspekcję, jest pewna wada. Jeden test zrzutowy da mniej przydatne informacje. Również własność prywatna „privateVariable” w „testOne” jest współdzielona we wszystkich instancjach, co jest pomocne, o czym wspomina shesek w odpowiedziach.
źródło
privateVariable
jest po prostu zmienną w zakresie IIFE i jest ona wspólna dla wszystkich instancji, więc nie powinieneś przechowywać na niej danych specyficznych dla instancji. (w testTwo jest to specyficzne dla instancji, ponieważ każde wywołanie testTwo () tworzy nowy zakres dla instancji)Dog.prototype
. Więc zamiast używaćthis.bark = function () {...}
, możemy zrobićDot.prototype.bark = function () {...}
pozaDog
funkcją. (Zobacz więcej szczegółów w tej odpowiedzi )function Shape(x, y) { this.x = x; this.y = y; } // 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver function Circle(x, y, r) { Shape.call(this, x, y); this.r = r; } // 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor Circle.prototype = Object.create(Shape.prototype);
źródło
Zajrzałbym do YUI i do
Base
biblioteki Deana Edwarda : http://dean.edwards.name/weblog/2006/03/base/W przypadku YUI możesz rzucić okiem na moduł lang , zwł. YAHOO.lang.extend metodą. Następnie możesz przejrzeć źródła niektórych widżetów lub narzędzi i zobaczyć, jak używają tej metody.
źródło
lang
jest częściowo uszkodzony. Czy ktoś chce to naprawić dla YUI 3?Istnieje również biblioteka Microsoft ASP.NET Ajax, http://www.asp.net/ajax/ .
Istnieje również wiele dobrych artykułów MSDN, w tym Tworzenie zaawansowanych aplikacji internetowych z technikami zorientowanymi obiektowo .
źródło
Oto najbardziej przejrzysty przykład, jaki znalazłem, z książki Mixu o Node ( http://book.mixu.net/node/ch6.html ):
źródło
ES6
class
iextends
ES6
class
iextends
są po prostu cukrem składniowym dla wcześniej możliwej manipulacji łańcuchem prototypów, a więc prawdopodobnie najbardziej kanoniczną konfiguracją.Najpierw dowiedz się więcej o łańcuchu prototypów i
.
wyszukiwaniu właściwości pod adresem : https://stackoverflow.com/a/23877420/895245Teraz zdekonstruujmy, co się dzieje:
class C { constructor(i) { this.i = i } inc() { return this.i + 1 } } class D extends C { constructor(i) { super(i) } inc2() { return this.i + 2 } }
// Inheritance syntax works as expected. (new C(1)).inc() === 2 (new D(1)).inc() === 2 (new D(1)).inc2() === 3
// "Classes" are just function objects. C.constructor === Function C.__proto__ === Function.prototype D.constructor === Function // D is a function "indirectly" through the chain. D.__proto__ === C D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class // lookups will work as expected var d = new D(1) d.__proto__ === D.prototype D.prototype.__proto__ === C.prototype // This is what `d.inc` actually does. d.__proto__.__proto__.inc === C.prototype.inc
// Class variables // No ES6 syntax sugar apparently: // /programming/22528967/es6-class-variable-alternatives C.c = 1 C.c === 1 // Because `D.__proto__ === C`. D.c === 1 // Nothing makes this work. d.c === undefined
Uproszczony diagram bez wszystkich predefiniowanych obiektów:
__proto__ (C)<---------------(D) (d) | | | | | | | | | |prototype |prototype |__proto__ | | | | | | | | | | | +---------+ | | | | | | | | | | v v |__proto__ (D.prototype) | | | | | | | | |__proto__ | | | | | | | | +--------------+ | | | | | | | v v | (C.prototype)--->(inc) | v Function.prototype
źródło
Proponuję zajrzeć do PrototypeJS 'Class.create:
Line 83 @ http://prototypejs.org/assets/2009/8/31/prototype.js
źródło
Najlepsze przykłady, jakie widziałem, znajdują się w JavaScript Douglasa Crockforda : The Good Parts . Zdecydowanie warto kupić, aby uzyskać wyważony pogląd na język.
Douglas Crockford jest odpowiedzialny za format JSON i pracuje w Yahoo jako guru JavaScript.
źródło
Istnieje fragment kodu JavaScript Dziedziczenie oparte na prototypach z implementacjami specyficznymi dla wersji ECMAScript. Automatycznie wybierze, która z implementacji ES6, ES5 i ES3 zostanie użyta, zgodnie z bieżącym czasem wykonywania.
źródło
Dodanie przykładu dziedziczenia opartego na prototypie w Javascript.
// Animal Class function Animal (name, energy) { this.name = name; this.energy = energy; } Animal.prototype.eat = function (amount) { console.log(this.name, "eating. Energy level: ", this.energy); this.energy += amount; console.log(this.name, "completed eating. Energy level: ", this.energy); } Animal.prototype.sleep = function (length) { console.log(this.name, "sleeping. Energy level: ", this.energy); this.energy -= 1; console.log(this.name, "completed sleeping. Energy level: ", this.energy); } Animal.prototype.play = function (length) { console.log(this.name, " playing. Energy level: ", this.energy); this.energy -= length; console.log(this.name, "completed playing. Energy level: ", this.energy); } // Dog Class function Dog (name, energy, breed) { Animal.call(this, name, energy); this.breed = breed; } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.bark = function () { console.log(this.name, "barking. Energy level: ", this.energy); this.energy -= 1; console.log(this.name, "done barking. Energy level: ", this.energy); } Dog.prototype.showBreed = function () { console.log(this.name,"'s breed is ", this.breed); } // Cat Class function Cat (name, energy, male) { Animal.call(this, name, energy); this.male = male; } Cat.prototype = Object.create(Animal.prototype); Cat.prototype.constructor = Cat; Cat.prototype.meow = function () { console.log(this.name, "meowing. Energy level: ", this.energy); this.energy -= 1; console.log(this.name, "done meowing. Energy level: ", this.energy); } Cat.prototype.showGender = function () { if (this.male) { console.log(this.name, "is male."); } else { console.log(this.name, "is female."); } } // Instances const charlie = new Dog("Charlie", 10, "Labrador"); charlie.bark(); charlie.showBreed(); const penny = new Cat("Penny", 8, false); penny.meow(); penny.showGender();
ES6 wykorzystuje znacznie łatwiejszą implementację dziedziczenia z użyciem konstruktora i super słów kluczowych.
źródło