Użycie „prototypu” kontra „to” w JavaScript?


776

Jaka jest różnica pomiędzy

var A = function () {
    this.x = function () {
        //do something
    };
};

i

var A = function () { };
A.prototype.x = function () {
    //do something
};


pojęcie tego słowa kluczowego zostało wyjaśnione tutaj scotch.io/@alZami/understanding-this-in-javascript
AL-zami 10.10.17

1
Czytanie „tego” wątku pokazuje, jak straszna jest JS i jak bardzo jej zasady są niejasne dla wielu programistów. Co jest dokładnie nie tak z łatwiejszymi do zrozumienia językami? Myślę, że nadszedł czas, aby programiści podnieśli głos, aby odrzucić mylące technologie, które nie mają żadnej lub mają niewielką wartość dla działalności biznesowej lub rozwojowej.
NoChance

Na obiekt a1.x !== a2.x:; na prototypie:a1.x === a2.x
Juan Mendes

Odpowiedzi:


467

Przykłady mają bardzo różne wyniki.

Przed spojrzeniem na różnice należy zwrócić uwagę na następujące kwestie:

  • Prototyp konstruktora umożliwia dzielenie się metodami i wartościami między instancjami za pośrednictwem prywatnej [[Prototype]]własności instancji .
  • Funkcja jest ten ustala, w jaki sposób funkcja jest wywoływana lub przez zastosowanie wiążą (nie omawiane tutaj). Gdy funkcja jest wywoływana na obiekcie (np. myObj.method()), To w metodzie odwołuje się do obiektu. Gdzie to nie jest ustawione przez wezwanie lub przez zastosowanie wiążą , to domyślnie globalnego obiektu (okno w przeglądarce) lub w trybie ścisłym, pozostaje niezdefiniowana.
  • JavaScript jest językiem obiektowym, tzn. Większość wartości to obiekty, w tym funkcje. (Ciągi, liczby i wartości logiczne nie są obiektami).

Oto fragmenty, o których mowa:

var A = function () {
    this.x = function () {
        //do something
    };
};

W tym przypadku zmiennej Aprzypisuje się wartość, która jest odwołaniem do funkcji. Gdy ta funkcja jest wywoływana przy użyciu A()funkcja jest to nie jest ustawiony przez wywołanie więc domyślnie globalnego obiektu i wyrażenie this.xjest skuteczna window.x. W rezultacie odniesienie do wyrażenia funkcyjnego po prawej stronie jest przypisane window.x.

W przypadku:

var A = function () { };
A.prototype.x = function () {
    //do something
};

dzieje się coś zupełnie innego. W pierwszym wierszu zmiennej Aprzypisuje się odwołanie do funkcji. W JavaScript wszystkie obiekty funkcji mają domyślnie właściwość prototypu, więc nie ma osobnego kodu do utworzenia obiektu A.prototype .

W drugim wierszu A.prototype.x jest przypisane odwołanie do funkcji. Spowoduje to utworzenie właściwości x, jeśli nie istnieje, lub przypisanie nowej wartości, jeśli tak jest. Różnica w stosunku do pierwszego przykładu, w którym właściwość x obiektu jest zaangażowana w wyrażenie.

Kolejny przykład znajduje się poniżej. Jest podobny do pierwszego (i może o co chciałeś zapytać):

var A = new function () {
    this.x = function () {
        //do something
    };
};

W tym przykładzie newoperator został dodany przed wyrażeniem funkcji, dzięki czemu funkcja jest wywoływana jako konstruktor. Kiedy wywołana newfunkcja jest ten ustawiony jest odwoływać się do nowego obiektu, którego prywatna [[Prototype]]własność jest ustawiona odwołać konstruktora publicznego prototypu . Tak więc w instrukcji przypisania xwłaściwość zostanie utworzona na tym nowym obiekcie. Funkcja wywoływana jako konstruktor domyślnie zwraca ten obiekt, więc nie ma potrzeby oddzielnej return this;instrukcji.

Aby sprawdzić, czy A ma właściwość x :

console.log(A.x) // function () {
                 //   //do something
                 // };

Jest to rzadkie użycie nowego, ponieważ jedynym sposobem na odniesienie się do konstruktora jest A.constructor . O wiele bardziej powszechne byłoby:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Innym sposobem osiągnięcia podobnego wyniku jest użycie natychmiast wywołanego wyrażenia funkcyjnego:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

W takim przypadku Aprzypisano wartość zwrotną wywołania funkcji po prawej stronie. Tutaj ponownie, ponieważ to nie jest ustawiony w zaproszeniu, będzie odnosić się do globalnego obiektu i this.xjest skuteczny window.x. Ponieważ funkcja nic nie zwraca, Abędzie miała wartość undefined.

Te różnice między tymi dwoma podejściami ujawniają się również, gdy serializujesz i usuwasz serializację obiektów JavaScript do / z JSON. Metody zdefiniowane w prototypie obiektu nie są serializowane podczas serializacji obiektu, co może być wygodne, gdy na przykład chcesz serializować tylko części danych obiektu, ale nie są to metody:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Powiązane pytania :

Sidenote: Pomiędzy tymi dwoma podejściami może nie być znacznych oszczędności pamięci, jednak użycie prototypu do udostępniania metod i właściwości prawdopodobnie zużyje mniej pamięci niż każda instancja posiadająca własną kopię.

JavaScript nie jest językiem niskiego poziomu. Myślenie o prototypowaniu lub innych wzorcach dziedziczenia jako sposobie jawnej zmiany sposobu przydzielania pamięci może nie być bardzo cenne.


49
@keparo: Mylisz się. Każdy obiekt ma [wewnętrzny] obiekt prototypowy (który może być null), ale to bardzo różni się od prototypewłaściwości - która dotyczy funkcji i do której jest ustawiany prototyp wszystkich instancji, gdy są one zbudowane new. Nie mogę uwierzyć, że to naprawdę zyskało 87 głosów pozytywnych :-(
Bergi

8
"The language is functional"czy jesteś pewien, że to właśnie oznacza funkcjonalność?
phant0m,

23
Popieram to, co @Bergi powiedział o prototypach. Funkcje mają właściwość prototypu. Wszystkie obiekty, w tym funkcje, mają inną właściwość wewnętrzną, do której można uzyskać dostęp za pomocą Object.getPrototypeOf (myObject) lub myObject .__ proto__ w niektórych przeglądarkach. Właściwość proto wskazuje element nadrzędny obiektu w łańcuchu prototypów (lub obiekt, z którego dziedziczy ten obiekt). Właściwość prototype (która dotyczy tylko funkcji) wskazała obiekt, który stanie się rodzicem wszelkich obiektów, które wykorzystują funkcję do tworzenia nowych obiektów za pomocą nowego słowa kluczowego.
Jim Cooper

11
Ten artykuł jest dość mylący i myli sposób jego ustawiania. Praca nad przepisem.
RobG

37
Ta odpowiedź jest dość dziwna i wydaje się, że całkowicie pomija sens pytania. Pytanie wydaje się być bardzo częste na temat definiowania właściwości typu w konstruktorze w porównaniu z protoype, ale połowa odpowiedzi dotyczy tego, co by się stało, gdybyś użył go Ajako funkcji, a druga połowa dotyczy niejasnych i niekonwencjonalnych sposobów działania coś prostego.
JLRishe,

235

Jak powiedzieli inni, pierwsza wersja, użycie „this” powoduje, że każda instancja klasy A ma własną niezależną kopię metody funkcji „x”. Natomiast użycie „prototypu” oznacza, że ​​każde wystąpienie klasy A będzie używać tej samej kopii metody „x”.

Oto kod pokazujący tę subtelną różnicę:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Jak wspomnieli inni, istnieją różne powody, aby wybrać jedną lub drugą metodę. Moja próbka ma jedynie wyraźnie pokazać różnicę.


5
Oczekiwałem, że tak się stanie, ale kiedy stworzyłem nowy obiekt po zmianie Axe jak wyżej, nadal wyświetlam „A”, chyba że użyję A jak singletonu. jsbin.com/omida4/2/edit
jellyfishtree

19
To dlatego, że mój przykład się mylił. To było złe tylko przez dwa lata. Westchnienie. Ale punkt jest nadal aktualny. Zaktualizowałem przykład o taki, który faktycznie działa. Dzięki za zwrócenie na to uwagi.
Benry

4
To metoda statyczna! : D

6
tak ... „prototyp” oznacza poziom statyczny lub klasowy…, który będzie współużytkowany przez wszystkie utworzone instancje… podczas gdy „this” to metoda instancji, która będzie miała własną kopię
Aneer Dev

7
To nie jest statyczne. Statyczny, stosowany w większości języków OO, oznacza, że ​​nie ma zależności od thisobiektu, który jest właścicielem metody. tzn. metoda nie ma obiektu będącego jej właścicielem. W tym przypadku istnieje thisobiekt, jak pokazano w klasie A w przykładzie.
CJStuart

152

Weź te 2 przykłady:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Większość osób (szczególnie najwyżej oceniane odpowiedzi) próbowała wyjaśnić, czym się różnią, nie wyjaśniając DLACZEGO. Myślę, że to źle i jeśli najpierw zrozumiesz podstawy, różnica stanie się oczywista. Spróbujmy najpierw wyjaśnić podstawy ...

a) Funkcja jest obiektem w JavaScript. KAŻDY obiekt w JavaScript otrzymuje właściwość wewnętrzną (co oznacza, że ​​nie możesz uzyskać do niej dostępu tak jak inne właściwości, z wyjątkiem być może przeglądarek takich jak Chrome), często nazywanych __proto__(możesz tak naprawdę wpisać anyObject.__proto__w Chrome, aby zobaczyć, do czego się odwołuje). , właściwość, nic więcej. Właściwość w JavaScript = zmienna wewnątrz obiektu, nic więcej. Co robią zmienne? Wskazują na rzeczy.

Więc na co wskazuje ta __proto__właściwość? Cóż, zwykle inny przedmiot (wyjaśnimy później). Jedynym sposobem wymuszenia JavaScript dla __proto__właściwości NIE wskazywania innego obiektu jest użycie var newObj = Object.create(null). Nawet jeśli to zrobisz, __proto__właściwość STILL istnieje jako właściwość obiektu, po prostu nie wskazuje innego obiektu, wskazuje null.

Oto, gdzie większość ludzi się myli:

Kiedy tworzysz nową funkcję w JavaScript (która jest również obiektem, pamiętasz?), W momencie jej zdefiniowania JavaScript automatycznie tworzy nową właściwość dla tej funkcji o nazwie prototype. Spróbuj:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypejest CAŁKOWICIE RÓŻNY od __proto__nieruchomości. W naszym przykładzie „A” ma teraz DWIE właściwości zwane „prototypem” i __proto__. To duże zamieszanie dla ludzi. prototypea __proto__właściwości nie są w żaden sposób powiązane, są oddzielnymi rzeczami wskazującymi na osobne wartości.

Możesz się zastanawiać: dlaczego JavaScript ma __proto__właściwość utworzoną na każdym obiekcie? Cóż, jedno słowo: delegacja . Kiedy wywołujesz właściwość obiektu, a obiekt go nie ma, JavaScript szuka obiektu, do którego się odwołuje, __proto__aby sprawdzić, czy może go mieć. Jeśli go nie ma, sprawdza __proto__właściwość tego obiektu i tak dalej ... aż do końca łańcucha. Stąd nazwa łańcucha prototypów . Oczywiście, jeśli __proto__nie wskazuje na obiekt, a zamiast tego wskazuje na nullszczęście, JavaScript zdaje sobie z tego sprawę i zwróci ci undefinedwłasność.

Możesz się również zastanawiać, dlaczego JavaScript tworzy właściwość wywoływaną prototypedla funkcji podczas jej definiowania? Ponieważ próbuje cię oszukać, tak oszuka cię , że działa jak języki oparte na klasach.

Przejdźmy do naszego przykładu i stwórzmy „obiekt” z A:

var a1 = new A();

Coś się dzieje w tle, kiedy to się stało. a1jest zwykłą zmienną, której przypisano nowy, pusty obiekt.

Fakt, że użyłeś operatora newprzed wywołaniem funkcji, A()zrobił coś DODATKOWEGO w tle. Słowo newkluczowe utworzyło nowy obiekt, który teraz się odwołuje, a1a ten obiekt jest pusty. Oto, co dzieje się dodatkowo:

Powiedzieliśmy, że w każdej definicji funkcji jest utworzona nowa właściwość o nazwie prototype(do której można uzyskać do niej dostęp, w przeciwieństwie do tej __proto__właściwości)? Cóż, ta właściwość jest teraz używana.

Jesteśmy teraz w punkcie, w którym mamy świeżo upieczony pusty a1przedmiot. Powiedzieliśmy, że wszystkie obiekty w JavaScript mają wewnętrzną __proto__właściwość, która wskazuje na coś ( a1również to ma), czy to null, czy inny obiekt. Co newoperator nie jest to, że ustawia tę __proto__właściwość, aby wskazywała funkcję w prototypenieruchomości. Przeczytaj to jeszcze raz. Zasadniczo jest to:

a1.__proto__ = A.prototype;

Powiedzieliśmy, że A.prototypeto nic więcej niż pusty obiekt (chyba że zmienimy go na coś innego przed zdefiniowaniem a1). Więc teraz w zasadzie a1.__proto__wskazuje na to samo A.prototype, co ten pusty obiekt. Oba wskazują na ten sam obiekt, który został utworzony, gdy ta linia się wydarzyła:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Teraz podczas var a1 = new A()przetwarzania instrukcji dzieje się coś innego . Zasadniczo A()jest wykonywany, a jeśli A jest coś takiego:

var A = function() { this.hey = function() { alert('from A') } };

Wszystkie te rzeczy w środku function() { }zostaną wykonane. Gdy dojdziesz do this.hey..linii, thiszmieni się na a1i otrzymasz:

a1.hey = function() { alert('from A') }

Nie będę wyjaśniać, dlaczego thiswprowadzono zmiany, a1ale jest to świetna odpowiedź, aby dowiedzieć się więcej.

Podsumowując, kiedy to zrobisz, var a1 = new A()w tle dzieją się 3 rzeczy:

  1. Całkowicie nowy pusty obiekt jest tworzony i przypisywany do a1.a1 = {}
  2. a1.__proto__właściwość jest przypisana do wskazywania tego samego, co A.prototypewskazuje na (inny pusty obiekt {})

  3. Funkcja A()jest wykonywana z thisustawionym nowym, pustym obiektem utworzonym w kroku 1 (przeczytaj odpowiedź, do której się odwoływałem powyżej, dlaczego thiswprowadzono zmiany a1)

Teraz spróbujmy stworzyć inny obiekt:

var a2 = new A();

Kroki 1, 2, 3 powtórzą się. Czy coś zauważyłeś? Kluczowym słowem jest powtórzenie. Krok 1: a2będzie nowym pustym obiektem, krok 2: jego __proto__właściwość wskaże tę samą rzecz, A.prototypea co najważniejsze, krok 3: funkcja A()zostanie PONOWNIE wykonana, co oznacza, że a2otrzyma heywłaściwość zawierającą funkcję. a1i a2nazwijmy dwie ODDZIELNE właściwości, heyktóre wskazują na 2 ODDZIELNE funkcje! Mamy teraz zduplikowane funkcje w tych samych dwóch różnych obiektach, które robią to samo, ups ... Możesz sobie wyobrazić implikacje pamięciowe, jeśli utworzymy 1000 obiektów new A, po tym, jak deklaracje wszystkich funkcji zajmują więcej pamięci niż coś takiego jak liczba 2. Więc jak temu zapobiec?

Pamiętasz, dlaczego __proto__właściwość istnieje na każdym obiekcie? Jeśli więc pobierzesz yoManwłaściwość a1(która nie istnieje), __proto__zostanie sprawdzona jej właściwość, która, jeśli jest to obiekt (i w większości przypadków tak jest), sprawdzi, czy zawiera yoMan, a jeśli nie, sprawdzi ten obiekt __proto__itp. Jeśli to zrobi, weźmie tę wartość właściwości i wyświetli ją.

Więc ktoś postanowił użyć tego faktu + faktu, że podczas tworzenia a1jego __proto__właściwość wskazuje na ten sam (pusty) obiekt A.prototypei robi to:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Fajne! Teraz, kiedy tworzysz a1, ponownie przechodzi przez wszystkie 3 powyższe kroki, aw kroku 3 nic nie robi, ponieważ function A()nie ma nic do wykonania. A jeśli zrobimy:

a1.hey

Zobaczy, że a1nie zawiera, heyi sprawdzi swój __proto__obiekt właściwości, aby zobaczyć, czy go ma, co ma miejsce.

Dzięki takiemu podejściu eliminujemy część z kroku 3, w której funkcje są powielane przy każdym tworzeniu nowego obiektu. Zamiast a1i a2posiadające odrębną heywłasność, teraz żaden z nich nie ma. Co, jak sądzę, do tej pory sam się zorientowałeś. To miła rzecz ... jeśli zrozumiesz, __proto__i Function.prototypetakie pytania będą dość oczywiste.

UWAGA: Niektóre osoby zwykle nie nazywają wewnętrznej właściwości Prototype, ponieważ __proto__użyłem tej nazwy pocztą, aby wyraźnie odróżnić ją od Functional.prototypewłaściwości jako dwie różne rzeczy.


1
Naprawdę dokładna i pouczająca odpowiedź. Zrobiłem kilka testów pamięci przy użyciu powyższych struktur obiektów (A.prototype.hey vs. object this.hey) i utworzyłem 1000 instancji każdego z nich. Ślad pamięciowy dla podejścia opartego na właściwościach obiektu był około 100 KB większy w porównaniu do prototypu. Następnie dodałem kolejną funkcję o tym samym celu, zwaną „głupią”, która wzrosła liniowo do 200 kb. Nieistotne, ale nie orzeszki ziemne.
jookyone

Co bardziej interesujące, metoda prototypowa była nieznacznie wolniejsza niż metoda właściwości obiektu działająca lokalnie. Ogólnie rzecz biorąc, nie jestem pewien, czy javascript powinien być używany do manipulacji danymi obiektów o numeracji powyżej 10k, dlatego też neguję wszelkie powody zmiany podejścia w oparciu o potencjalne efekty pamięci. W tym momencie praca powinna zostać odciążona na serwerze.
jookyone

Chodzi o to __proto__i .prototypesą zupełnie inne rzeczy.
Wayou,

1
Nie czuję się spełniony, aby tylko dać ci upvote ... dobrze!
Kristianmitk,

58

W większości przypadków są one zasadniczo takie same, ale druga wersja oszczędza pamięć, ponieważ istnieje tylko jedno wystąpienie funkcji zamiast oddzielnej funkcji dla każdego obiektu.

Powodem korzystania z pierwszego formularza jest dostęp do „członków prywatnych”. Na przykład:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Ze względu na reguły zakresu javascript, private_var jest dostępne dla funkcji przypisanej do this.x, ale nie poza obiektem.


1
Zobacz ten post: stackoverflow.com/a/1441692/654708, aby zobaczyć, jak uzyskać dostęp do prywatnych członków za pośrednictwem prototypów.
GFoley83

@ GFoley83 ta odpowiedź nie pokazuje, że - prototypowe metody mogą uzyskać dostęp tylko do „publicznych” właściwości danego obiektu. Tylko uprzywilejowane metody (nie w prototypie) mogą uzyskać dostęp do prywatnych członków.
Alnitak,

27

Pierwszy przykład zmienia interfejs tylko dla tego obiektu. Drugi przykład zmienia interfejs dla wszystkich obiektów tej klasy.


Oba udostępnią funkcję xdla wszystkich obiektów, których prototypowi przypisano nową instancję A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams

21

Ostatecznym problemem związanym z używaniem thiszamiast tego prototypejest to, że podczas przesłonięcia metody konstruktor klasy podstawowej nadal będzie odwoływał się do przesłoniętej metody. Rozważ to:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

przeciw:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Jeśli uważasz, że to nie jest problem, zależy to od tego, czy możesz żyć bez zmiennych prywatnych i czy masz wystarczające doświadczenie, aby poznać wyciek, gdy go zobaczysz. Również niewygodne jest umieszczanie logiki konstruktora po definicjach metod.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

przeciw:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1

20

Każdy obiekt jest powiązany z obiektem prototypowym. Podczas próby uzyskania dostępu do właściwości, która nie istnieje, JavaScript przeszuka obiekt prototypowy obiektu pod kątem tej właściwości i zwróci ją, jeśli istnieje.

prototypeWłaściwością konstruktora funkcji odnosi się do obiektu prototypu wszystkich przypadkach utworzone z tej funkcji przy użyciu new.


W pierwszym przykładzie dodajesz właściwość xdo każdej instancji utworzonej za pomocą Afunkcji.

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

W drugim przykładzie dodajesz właściwość do obiektu prototypowego, do którego wskazują wszystkie instancje A.

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

Podsumowując, w pierwszym przykładzie do każdej instancji przypisana jest kopia funkcji . W drugim przykładzie pojedyncza kopia funkcji jest wspólna dla wszystkich instancji .


1
Zagłosowałam za najbardziej bezpośrednią odpowiedź na pytanie.
Nick Pineda

1
Podobało mi się twoje bezpośrednie podejście !! kciuki w górę!
Książę Vijay Pratap

16

Co za różnica? => Dużo.

Myślę, że thiswersja służy do enkapsulacji, czyli ukrywania danych. Pomaga manipulować zmiennymi prywatnymi.

Spójrzmy na następujący przykład:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Teraz prototypestrukturę można zastosować w następujący sposób:

Różni dorośli mają różny wiek, ale wszyscy dorośli mają te same prawa.
Tak więc dodajemy go za pomocą prototypu, a nie tego.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Przyjrzyjmy się teraz implementacji.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Mam nadzieję że to pomoże.


3
+1 O wiele mniej skomplikowana i bardziej graficzna odpowiedź niż pozostałe. Ale powinieneś rozwinąć nieco więcej, zanim podasz te (dobre) przykłady.
yerforkferchips

1
Nie jestem pewien, czy „ta wersja służy do enkapsulacji, tj. Ukrywania danych”. Jeśli właściwość wewnątrz funkcji jest zdefiniowana za pomocą „this” jak w „this.myProperty = ...”, taka właściwość nie jest „prywatna” i można uzyskać do niej dostęp z obiektów spoza klasy za pomocą „new”.
NoChance,

14

Prototyp jest szablonem klasy; co dotyczy wszystkich jego przyszłych przypadków. Jest to szczególny przypadek obiektu.


14

Wiem, że na to odpowiedziano na śmierć, ale chciałbym pokazać rzeczywisty przykład różnic prędkości.

Funkcja bezpośrednio na obiekcie

Funkcja na prototypie

Tutaj tworzymy 2 miliony nowych obiektów za pomocą printmetody w Chrome. Przechowujemy każdy obiekt w tablicy. Założenie printprototypu zajmuje około 1/2 czasu.


13

Pozwól, że dam ci bardziej wyczerpującą odpowiedź, której nauczyłem się podczas szkolenia JavaScript.

Większość odpowiedzi wspomniała już o różnicy, tj. Kiedy prototypowanie funkcji jest wspólne dla wszystkich (przyszłych) instancji. Deklarowanie funkcji w klasie spowoduje utworzenie kopii dla każdej instancji.

Ogólnie rzecz biorąc, nie ma dobrego ani złego, to bardziej kwestia gustu lub decyzji projektowej w zależności od twoich wymagań. Prototyp jest jednak techniką rozwijaną w sposób obiektowy, jak mam nadzieję, zobaczycie na końcu tej odpowiedzi.

W swoim pytaniu pokazałeś dwa wzory. Spróbuję wyjaśnić jeszcze dwa i w razie potrzeby spróbuję wyjaśnić różnice. Możesz edytować / rozszerzać. We wszystkich przykładach chodzi o obiekt samochodowy, który ma lokalizację i może się poruszać.

Wzór dekoratora obiektów

Nie jestem pewien, czy ten wzór jest nadal aktualny, ale istnieje. I dobrze o tym wiedzieć. Po prostu przekazujesz obiekt i właściwość do funkcji dekoratora. Dekorator zwraca obiekt za pomocą właściwości i metody.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Klasy funkcjonalne

Funkcja w JavaScript jest specjalizowanym obiektem. Oprócz wywołania funkcja może przechowywać właściwości jak każdy inny obiekt.

W tym przypadku Carjest funkcja ( również myśl obiekt ), którą można wywołać tak jak zwykle. Ma właściwość methods(która jest obiektem z movefunkcją). Po Carwywołaniu wywoływana jest extendfunkcja, która działa magicznie i rozszerza Carfunkcję (myśl obiekt) o zdefiniowane metody methods.

Ten przykład, choć inny, jest najbliższy pierwszemu przykładowi w pytaniu.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Klasy prototypowe

Pierwsze dwa wzorce umożliwiają dyskusję na temat stosowania technik do definiowania wspólnych metod lub stosowania metod, które są zdefiniowane bezpośrednio w ciele konstruktora. W obu przypadkach każda instancja ma swoją movefunkcję.

Wzorzec prototypowy nie nadaje się dobrze do tego samego badania, ponieważ współdzielenie funkcji za pośrednictwem delegacji prototypów jest właśnie celem wzorca prototypowego. Jak zauważyli inni, oczekuje się, że będzie mieć lepszą pamięć.

Jednak interesujący jest jeden punkt: każdy prototypeobiekt ma właściwość wygody constructor, która wskazuje na funkcję (obiekt myślowy), do której został dołączony.

Odnośnie trzech ostatnich linii:

W tym przykładzie Carłączy się z prototypeobiektem, który łączy się constructorz Carsamym sobą, tj. Car.prototype.constructorJest Carsobą. To pozwala dowiedzieć się, która funkcja konstruktora zbudowała określony obiekt.

amy.constructorWyszukiwanie kończy się niepowodzeniem i dlatego jest delegowane do Car.prototype, który ma właściwość konstruktora. I tak amy.constructorjest Car.

Ponadto amyjest instanceof Car. instanceofOperator działa, sprawdzając, czy prototyp obiektu prawy argument za ( Car) można znaleźć nigdzie w prototypie lewy argument jest ( amy) łańcucha.

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Na początku niektórzy programiści mogą się mylić. Zobacz poniższy przykład:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

Te instanceofpowroty operatora false, ponieważ Dog„s prototyp nie można znaleźć nigdzie w fido” s łańcucha prototypów. fidojest prostym obiektem, który jest tworzony za pomocą literału obiektu, tj. po prostu deleguje się do Object.prototype.

Wzory pseudoklasyczne

Jest to naprawdę kolejna forma wzorca prototypowego w formie uproszczonej i bardziej znana osobom, które programują na przykład w Javie, ponieważ używa ona newkonstruktora.

Robi to samo, co w przypadku wzoru prototypowego, jest to po prostu cukier syntaktyczny ponad wzorem prototypowym.

Jednak podstawowa różnica polega na tym, że w silnikach JavaScript są zaimplementowane optymalizacje, które mają zastosowanie tylko w przypadku stosowania wzorca pseudoklasycznego. Pomyśl o pseudoklasycznym wzorze prawdopodobnie szybszej wersji wzoru prototypowego; relacje między obiektami w obu przykładach są takie same.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Wreszcie nie powinno być zbyt trudne uświadomienie sobie, jak można realizować programowanie obiektowe. Istnieją dwie sekcje.

Jedna sekcja, która definiuje wspólne właściwości / metody w prototypie (łańcuch).

I kolejna sekcja, w której umieścisz definicje, które odróżniają obiekty od siebie ( loczmienna w przykładach).

To pozwala nam stosować pojęcia takie jak nadklasa lub podklasa w JavaScript.

Dodaj lub edytuj. Po ukończeniu mógłbym zrobić z tego wiki społeczności.


Nie zapukałem bardzo dokładnego postu, ale myślałem, że OO i dziedzictwo prototypowe to zasadniczo różne szkoły myśli.
Nick Pineda

Są, ale można „robić OO” różnymi technikami / myślami, prawda?
Ely,

Nie jestem pewien. Wielu po prostu twierdzi, że prototypowa filozofia jest po prostu inna i wielu próbuje porównać ją do OO, ponieważ jest to szkoła myślenia, do której wielu się przyzwyczai.
Nick Pineda

Chodzi mi o to, że jeśli chcesz ćwiczyć styl OO, a język oferuje zestaw technik, które pomagają to zrobić, niekoniecznie jest to złe.
Ely,

11

Uważam, że @Matthew Crumley ma rację. Są funkcjonalnie , jeśli nie strukturalnie, równoważne. Jeśli użyjesz Firebug, aby spojrzeć na obiekty, które zostały utworzone za pomocą new, możesz zobaczyć, że są one takie same. Jednak moje preferencje byłyby następujące. Domyślam się, że to bardziej przypomina to, do czego jestem przyzwyczajony w C # / Java. To znaczy, zdefiniuj klasę, zdefiniuj pola, konstruktor i metody.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDYCJA Nie chciałem sugerować, że zakres zmiennej był prywatny, ja tylko próbowałem zilustrować, jak definiuję swoje klasy w javascript. Zmienna nazwa została zmieniona, aby to odzwierciedlić.


2
_instance_var jak we właściwości initializei x methods do not refer to the _instance_var` w Ainstancji, ale do globalnej. Użyj, this._instance_varjeśli chcesz użyć _instance_varwłaściwości Ainstancji.
Lekensteyn,

2
Zabawne jest to, że Benry również popełnił taki błąd, który został odkryty również po dwóch latach: p
Lekensteyn

10

Jak omówiono w innych odpowiedziach, jest to naprawdę kwestia wydajności, ponieważ funkcja w prototypie jest wspólna dla wszystkich instancji - zamiast funkcji tworzonej dla każdej instancji.

Złożyłem jsperf, aby to pokazać. Istnieje ogromna różnica w czasie potrzebnym do utworzenia instancji klasy, chociaż tak naprawdę jest to istotne tylko w przypadku tworzenia wielu instancji.

http://jsperf.com/functions-in-constructor-vs-prototype


8

Pomyśl o języku o typie statycznym, rzeczy prototypesą statyczne, a rzeczy thiszwiązane z instancjami.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.