2015-02-28 7 views
65

System prototyp wygląda o wiele bardziej elastyczne niż tradycyjny system klasy, ale ludzie wydają się czuć zadowolony z tak zwanych „dobrych praktyk”, które naśladują tradycyjny system Klasa:Co może zrobić prototyp systemu JavaScript poza naśladowaniem klasycznego systemu klasy?

function foo() { 
    // define instance properties here 
} 

foo.prototype.method = //define instance method here 

new foo() 

muszą istnieć inne rzeczy że prototypowy system może działać z całą elastycznością.

Czy istnieją zastosowania systemu prototypowego poza udawaniem klas? Jakie rzeczy mogą robić prototypy, których klasy nie mogą lub nie istnieją?

+0

powiązane, jeśli nie jest duplikatem: [Czy jakaś biblioteka JavaScript używa dynamicznych aspektów prototypowego systemu?] (Http://stackoverflow.com/q/10609822/1048572) – Bergi

+0

Zgaduję, że znacznie łatwiej jest dynamicznie dodawać metody i korzystając z prototypów, możesz bardzo łatwo rozszerzyć swoją "klasę". – theonlygusti

+4

Warto nadmienić, że klasyczny system klasy może również trywialnie naśladować dziedziczenie prototypowe. –

Odpowiedz

46

Prototypowy system oferuje zniewalający model metaprogramming, wdrażając dziedziczenie za pomocą standardowych obiektów. Oczywiście jest to najczęściej używane do wyrażenia ustalonej i prostej koncepcji klas instancji, ale bez klas jako struktur niezmiennych na poziomie języka, które potrzebują specyficznej składni, aby je utworzyć. Używając zwykłych obiektów, wszystko co możesz zrobić dla obiektów (i możesz zrobić wszystko) możesz teraz zrobić z "klasami" - jest to elastyczność, o której mówisz.

Elastyczność ta jest następnie wykorzystywana wiele do rozszerzenia i zmieniać klasy programowo, przy użyciu wyłącznie podanych możliwości obiektowo mutacja javascript:

  • wstawek i cechy do wielokrotnego dziedziczenia
  • prototypy mogą być zmienione po obiektach które dziedziczą z nich zostały wystąpienia
  • funkcje wyższego rzędu i sposób dekoratorzy mogą być łatwo wykorzystane w tworzeniu prototypów

Oczywiście sam prototypowy model ma większą moc niż implementacja klas. Cechy te są stosowane raczej rzadko, jako że pojęcie klasy jest bardzo przydatne i rozpowszechnione, więc rzeczywiste uprawnienia prototypowego dziedziczenia nie są dobrze znane (i nie jest dobrze zoptymalizowany w silnikach JS: - /)

  • przełączania się prototypy istniejących obiektów mogą radykalnie zmienić ich zachowanie. (pełna obsługa przychodzi z ES6 Reflect.setPrototypeOf)
  • kilka wzorów inżynierii oprogramowania może być zaimplementowanych bezpośrednio z obiektami. Przykładami są: flyweight pattern o właściwościach, w tym dynamiczne łańcuchy, oh i oczywiście prototype pattern.

    Dobrym przykładem na ostatni będzie obiekt opcji z wartościami domyślnymi. Każdy tworzy je za pomocą

    var myOptions = extend({}, defaultOptions, optionArgument); 
    

    bardziej dynamiczne podejście byłoby użyć

    var myOptions = extend(Object.create(defaultOptions), optionArgument); 
    
+0

Czy jest jakaś przewaga polegająca na rozszerzeniu 'myOptions' przy użyciu bardziej dynamicznego podejścia? Zazwyczaj obiekt konfiguracyjny pozostaje identyczny w trakcie wywołania funkcji. – Kay

+0

@Kay: Będzie mniejszy (mniej pamięci) i powinien zostać utworzony szybciej, zwłaszcza w przypadku dużych obiektów domyślnych. Również zmiany wartości domyślnych automatycznie propagowałyby – Bergi

11

Myślę, że prototypal System dziedziczenia pozwala na znacznie bardziej dynamiczne dodawanie metod/właściwości.

Możesz łatwo rozszerzać klasy napisane przez inne osoby, np. Wszystkie wtyczki jQuery, a także możesz łatwo dodać do klas natywnych, dodawać funkcje narzędziowe do łańcuchów, tablic i cóż.

Przykład:

// I can just add whatever I want to anything I want, whenever I want 
String.prototype.first = function(){ return this[0]; }; 

'Hello'.first() // == 'H' 

Można również kopiować metod z innych klas,

function myString(){ 
    this[0] = '42'; 
} 
myString.prototype = String.prototype; 

foo = new myString(); 
foo.first() // == '42' 

Oznacza to również można przedłużyć prototyp po obiekt odziedziczyła po nim, ale te zmiany zostaną zastosowane.

I, osobiście uważam, że prototypy być naprawdę wygodne i proste metody układania się wewnątrz obiektu jest bardzo atrakcyjne dla mnie;)

32

W czerwcu 2013 I odpowiedział na pytanie o benefits of prototypal inheritance over classical. Od tego czasu spędziłem dużo czasu zastanawiając się nad dziedziczeniem, zarówno prototypowym, jak i klasycznym, i napisałem obszernie o tym, jak prototype-classisomorphism.

Tak, podstawowym zastosowaniem prototypowego dziedziczenia jest symulacja zajęć. Można go jednak używać znacznie częściej niż po prostu symulować klasy. Na przykład łańcuchy prototypów są bardzo podobne do łańcuchów zasięgu.

Prototype-Scope Izomorfizm także

Prototypy i zasięgi w JavaScripcie mają ze sobą wiele wspólnego. W JavaScript są trzy popularne typy łańcuchów:

  1. Łańcuchy prototypów.

    var foo = {}; 
    var bar = Object.create(foo); 
    var baz = Object.create(bar); 
    
    // chain: baz -> bar -> foo -> Object.prototype -> null 
    
  2. Łańcuchy zasięgu.

    function foo() { 
        function bar() { 
         function baz() { 
          // chain: baz -> bar -> foo -> global 
         } 
        } 
    } 
    
  3. Łańcuchy metod.

    var chain = { 
        foo: function() { 
         return this; 
        }, 
        bar: function() { 
         return this; 
        }, 
        baz: function() { 
         return this; 
        } 
    }; 
    
    chain.foo().bar().baz(); 
    

z trzech, łańcuchy prototypowe i łańcuchy zakres są najbardziej zbliżone. W rzeczywistości można dołączyć łańcuch prototypów do łańcucha zasięgu za pomocą instrukcji notoriouswith.

function foo() { 
    var bar = {}; 
    var baz = Object.create(bar); 

    with (baz) { 
     // chain: baz -> bar -> Object.prototype -> foo -> global 
    } 
} 

Więc jaki jest pożytek z izomorfizmu w zakresie prototypu? Jednym z bezpośrednich zastosowań jest modelowanie łańcuchów zasięgu za pomocą łańcuchów prototypów. Dokładnie to zrobiłem dla mojego własnego języka programowania Bianca, który zaimplementowałem w JavaScript.

raz pierwszy zdefiniował globalny zakres Bianca, zapełnianie go z wieloma przydatnymi funkcjami matematycznymi w pliku o wdzięcznej nazwie global.js następująco:

var global = module.exports = Object.create(null); 

global.abs = new Native(Math.abs); 
global.acos = new Native(Math.acos); 
global.asin = new Native(Math.asin); 
global.atan = new Native(Math.atan); 
global.ceil = new Native(Math.ceil); 
global.cos = new Native(Math.cos); 
global.exp = new Native(Math.exp); 
global.floor = new Native(Math.floor); 
global.log = new Native(Math.log); 
global.max = new Native(Math.max); 
global.min = new Native(Math.min); 
global.pow = new Native(Math.pow); 
global.round = new Native(Math.round); 
global.sin = new Native(Math.sin); 
global.sqrt = new Native(Math.sqrt); 
global.tan = new Native(Math.tan); 

global.max.rest = { type: "number" }; 
global.min.rest = { type: "number" }; 

global.sizeof = { 
    result: { type: "number" }, 
    type: "function", 
    funct: sizeof, 
    params: [{ 
     type: "array", 
     dimensions: [] 
    }] 
}; 

function Native(funct) { 
    this.funct = funct; 
    this.type = "function"; 
    var length = funct.length; 
    var params = this.params = []; 
    this.result = { type: "number" }; 
    while (length--) params.push({ type: "number" }); 
} 

function sizeof(array) { 
    return array.length; 
} 

pamiętać, że stworzył globalny zakres korzystania Object.create(null). Zrobiłem to, ponieważ zasięg globalny nie ma żadnego zakresu nadrzędnego.

Następnie dla każdego programu stworzyłem osobny zakres programu, który zawiera definicje najwyższego poziomu programu. Kod jest przechowywany w pliku o nazwie analyzer.js, który jest zbyt duży, aby zmieścić się w jednej odpowiedzi.Oto pierwsze trzy wiersze pliku:

var parse = require("./ast"); 
var global = require("./global"); 
var program = Object.create(global); 

Jak widać, zakres globalny jest rodzajem zakresu programu. W związku z tym program dziedziczy po global, dzięki czemu wyszukiwanie zmiennej zakresu jest tak proste, jak wyszukiwanie właściwości obiektu. To sprawia, że ​​środowisko wykonawcze języka jest znacznie prostsze.

Zakres programu zawiera definicje najwyższego poziomu programu. Na przykład, rozważmy następujący program mnożenia macierzy, które są zapisane w pliku matrix.bianca:

col(a[3][3], b[3][3], i, j) 
    if (j >= 3) a 
    a[i][j] += b[i][j] 
    col(a, b, i, j + 1) 

row(a[3][3], b[3][3], i) 
    if (i >= 3) a 
    a = col(a, b, i, 0) 
    row(a, b, i + 1) 

add(a[3][3], b[3][3]) 
    row(a, b, 0) 

Definicje najwyższego poziomu col, row i add. Każda z tych funkcji ma swój własny zakres funkcji, który dziedziczy po zasięgu programu. Kod dla które można znaleźć na line 67 of analyzer.js:

scope = Object.create(program); 

Na przykład, zakres funkcji add ma definicje macierzy a i b.

W związku z tym prototypy obok klas są również przydatne do modelowania zakresów funkcji.

Prototypy dla typów danych modelowanie algebraicznych

Klasy nie są jedynym dostępnym rodzajem abstrakcji. W językach programowania funkcjonalnego dane są modelowane przy użyciu algebraic data types.

Najlepszym przykładem algebraicznych typu danych jest to, że z listy:

data List a = Nil | Cons a (List a) 

Ta definicja danych oznacza po prostu, że wykaz a może być albo pustą listę (tj Nil) albo wartości wpisz “ a ” wstawiony do listy a (tzn. Cons a (List a)). Na przykład, następujące są wszystkie listy:

Nil       :: List a 
Cons 1 Nil     :: List Number 
Cons 1 (Cons 2 Nil)   :: List Number 
Cons 1 (Cons 2 (Cons 3 Nil)) :: List Number 

Typem zmiennej a w definicji danych umożliwia parametric polymorphism (tj pozwala listę do przechowywania wszelkiego rodzaju wartości). Na przykład Nil może być wyspecjalizowany do listy liczb lub listy booleans, ponieważ ma ona typ List a, gdzie a może być dowolna.

To pozwala nam tworzyć funkcje parametryczne jak length:

length :: List a -> Number 
length Nil  = 0 
length (Cons _ l) = 1 + length l 

Funkcja length mogłyby zostać użyte do znalezienia długości dowolnej listy, niezależnie od rodzaju wartości w niej zawartych, ponieważ funkcja length prostu nie robi dbaj o wartości na liście.

Oprócz polimorfizmu parametrycznego większość funkcjonalnych języków programowania ma również pewną postać ad-hoc polymorphism. W przypadku polimorfizmu ad-hoc wybrana jest jedna konkretna implementacja funkcji w zależności od rodzaju zmiennej polimorficznej.

Na przykład operator + w kodzie JavaScript jest używany zarówno do dodawania, jak i łączenia ciągów w zależności od typu argumentu.Jest to forma polimorfizmu ad-hoc.

Podobnie w funkcjonalnych językach programowania funkcja map jest zwykle przeciążana. Na przykład możesz mieć inną implementację map dla list, inną implementację dla zestawów itp. Klasy typów są jednym ze sposobów implementacji polimorfizmu ad-hoc. Na przykład, klasa Functor typ zapewnia map funkcję:

class Functor f where 
    map :: (a -> b) -> f a -> f b 

Następnie tworzymy konkretne przypadki Functor dla różnych typów danych:

instance Functor List where 
    map :: (a -> b) -> List a -> List b 
    map _ Nil  = Nil 
    map f (Cons a l) = Cons (f a) (map f l) 

Prototypy w JavaScript pozwalają modelować zarówno algebraiczne typy danych i polimorfizm ad-hoc. Na przykład, powyższy kod może być tłumaczone jeden do jednego do JavaScript w następujący sposób:

var list = Cons(1, Cons(2, Cons(3, Nil))); 
 

 
alert("length: " + length(list)); 
 

 
function square(n) { 
 
    return n * n; 
 
} 
 

 
var result = list.map(square); 
 

 
alert(JSON.stringify(result, null, 4));
<script> 
 
// data List a = Nil | Cons a (List a) 
 

 
function List(constructor) { 
 
    Object.defineProperty(this, "constructor", { 
 
     value: constructor || this 
 
    }); 
 
} 
 

 
var Nil = new List; 
 

 
function Cons(head, tail) { 
 
    var cons = new List(Cons); 
 
    cons.head = head; 
 
    cons.tail = tail; 
 
    return cons; 
 
} 
 

 
// parametric polymorphism 
 

 
function length(a) { 
 
    switch (a.constructor) { 
 
    case Nil: return 0; 
 
    case Cons: return 1 + length(a.tail); 
 
    } 
 
} 
 

 
// ad-hoc polymorphism 
 

 
List.prototype.map = function (f) { 
 
    switch (this.constructor) { 
 
    case Nil: return Nil; 
 
    case Cons: return Cons(f(this.head), this.tail.map(f)); 
 
    } 
 
}; 
 
</script>

Chociaż klasy mogą być wykorzystane do modelowania polimorfizm ad-hoc, jak również wszystkie przeciążone funkcje trzeba zdefiniować w jednym miejscu. Dzięki prototypom możesz je zdefiniować w dowolnym miejscu.

Wniosek

Jak widać, prototypy są bardzo uniwersalne. Tak, są używane głównie do modelowania klas. Można je jednak wykorzystać do wielu innych rzeczy.

Niektóre z innych rzeczy, które prototypy mogą być wykorzystywane do:

  1. Tworzenie persistent data structures z podziału strukturalnego.

    Podstawową ideą podziału strukturalnego jest to, że zamiast modyfikowania obiektu, należy utworzyć nowy obiekt, który dziedziczy z oryginalnego obiektu i dokonywać żadnych modyfikacji ty chcieć. Dziedzictwo prototypowe jest w tym doskonałe.

  2. Jak wspominali inni, prototypy są dynamiczne. W związku z tym można z mocą wsteczną dodawać nowe prototypowe metody i będą one automatycznie dostępne we wszystkich instancjach prototypu.

Mam nadzieję, że to pomoże.

+1

A (zbyt) długie, ale zabawne czytanie :-) Jednak, wyjaśniając izomorfizm klasy prototypowej, nie trafia w sedno pytania - OP już wie, jak te działają, on chce wiedzieć, co jest poza tym. Jedyne wspomniane przez ciebie cechy to to, że prototypy mogą być użyte do wdrożenia łańcucha zasięgu (bardzo interesujący przykład) i pozwalają na dodawanie metod w dowolnym miejscu (co wydaje się być niezbędne do implementacji ADT). – Bergi

+1

Przejdę teraz do czytania artykułów o trwałych strukturach danych. Zastanawiam się, w jaki sposób można wykorzystać prototypy do ich implementacji bez wycieku starych danych. – Bergi

+0

Masz rację. Ograniczę długość odpowiedzi, usuwając izomorfizm klasy prototypowej. –

0

W języku JavaScript nie ma takiej koncepcji klasy. Tutaj wszystko jest przedmiotem. Wszystkie obiekty w kodzie JavaScript są niedostępne od Object. Prototypowa właściwość pomaga w dziedziczeniu, gdy tworzymy aplikację w sposób zorientowany obiektowo. W prototypie jest więcej funkcji niż w klasycznej strukturze obiektowej.

W prototypie można dodać właściwości do funkcji, które zostały napisane przez kogoś innego.

Na przykład.

Array.prototype.print=function(){ 
    console.log(this); 
} 

Zastosowanie w dziedziczenia:

Można użyć dziedziczenia przez stosując właściwość prototypu. Here to sposób dziedziczenia z JavaScript.

W tradycyjnym systemie klasy nie można modyfikować po zdefiniowaniu klasy. Ale możesz zrobić w JavaScript z prototypowym systemem.

Powiązane problemy