Kiedy używać programowania prototypowego w JavaScript

15

Sporo czasu poświęciłem na tworzenie prostych widżetów dla projektów w następujący sposób:

var project = project || {};

(function() {

  project.elements = {
    prop1: val1,
    prop2: val2
  }

  project.method1 = function(val) {
    // Do this
  }

  project.method2 = function(val) {
    // Do that
  }

  project.init = function() {
    project.method1(project.elements.prop1)
    project.method2(project.elements.prop2)
  }
})()

project.init();

Ale zacząłem zmieniać format na następujący:

function Project() {
  this.elements = {
    prop1: val1,
    prop2: val2
  }

  this.method_one(this.elements.prop1);
  this.method_two(this.elements.prop2);
}

Project.prototype.method_one = function (val) {
  // 
};

Project.prototype.method_two = function (val) {
  //
};

new Project();

To prawda, są to głupie przykłady, więc nie owijaj się wokół osi. Ale jaka jest różnica funkcjonalna i kiedy powinienem wybrać jedną lub drugą?

JDillon522
źródło
Myślę, że twój pierwszy przykład kodu nie zadziała, ponieważ projekt nie wykracza poza zakres. Aby użyć dziedziczenia w JS, używasz prototypowania. Pierwszy przykład nie ma na celu rozszerzenia projektu, więc możliwość ponownego użycia może być ograniczona.
Aitch
Tak, przypadkowo wstawiłem projectdeklarację do funkcji. Zaktualizowano
JDillon522,
to samo tutaj. pierwszy przykład jest statyczny, drugi jest zorientowany obiektowo.
Aitch
1
Jeśli używasz tylko JEDNEJ instancji obiektu, nie ma powodu, aby używać definicji prototypowej. Wydaje się, że potrzebujesz - jest to modułowy moduł - jest na to wiele opcji, patrz: addyosmani.com/writing-modular-js
c69,
1
Pierwszy wzór singletonu Drugi wzór singletonu (gdy 1 klasa może mieć wiele obiektów)
Kokizzu

Odpowiedzi:

6

Pierwszą różnicę można streścić w następujący sposób: thisodnosi się do wystąpienia klasy. prototypeodnosi się do definicji .

Powiedzmy, że mamy następującą klasę:

var Flight = function ( number ) { this.number = number; };

Tak więc tutaj przywiązujemy się this.numberdo każdej instancji klasy i ma to sens, ponieważ każdy Flightpowinien mieć swój własny numer lotu.

var flightOne = new Flight( "ABC" );
var flightTwo = new Flight( "XYZ" );

W przeciwieństwie, prototype definiuje pojedynczą właściwość, do której mogą mieć dostęp wszystkie instancje.

Teraz, jeśli chcemy uzyskać numer lotu, możemy po prostu napisać następujący fragment kodu, a wszystkie nasze instancje otrzymają odniesienie do tego nowo prototypowanego obiektu.

Flight.prototype.getNumber = function () { return this.number; };

Druga różnica dotyczy sposobu, w jaki JavaScript wyszukuje właściwość obiektu. Kiedy szukasz Object.whatever, JavaScript dociera aż do głównego obiektu (obiektu, który odziedziczył wszystko inne), a gdy tylko znajdzie dopasowanie, zwróci go lub wywoła.

Ale dzieje się tak tylko w przypadku prototypowanych właściwości. Więc jeśli masz gdzieś na wyższych poziomach this.whatever, JavaScript nie uzna tego za dopasowanie i kontynuuje wyszukiwanie.

Zobaczmy, jak to się dzieje w rzeczywistości.

Pierwsza uwaga, że ​​[prawie] wszystko to Obiekty w JavaScript. Spróbuj tego:

typeof null

Zobaczmy teraz, co jest w środku Object(zwróć uwagę na wielkie litery Oi .na końcu). Po wejściu do Narzędzi programistycznych Google Chrome .zobaczysz listę dostępnych właściwości w tym konkretnym obiekcie.

Object.

Teraz zrób to samo dla Function:

Function.

Możesz zauważyć namemetodę. Po prostu uruchom go i zobaczmy, co się stanie:

Object.name
Function.name

Teraz stwórzmy funkcję:

var myFunc = function () {};

Zobaczmy też, czy mamy nametutaj metodę:

myFunc.name

Powinieneś dostać pusty ciąg, ale to dobrze. Nie powinieneś otrzymywać błędu ani wyjątku.

Teraz dodajmy coś do tego boskiego Objecti zobaczmy, czy dostaniemy to również w innych miejscach?

Object.prototype.test = "Okay!";

I proszę bardzo:

Object.prototype.test
Function.prototype.test
myFunc.prototype.test

We wszystkich przypadkach powinieneś zobaczyć "Okay!".

Jeśli chodzi o zalety i wady każdej metody, można rozważyć prototypowanie jako „bardziej wydajny” sposób wykonywania zadań, ponieważ zachowuje on referencje w każdym przypadku, a raczej kopiuje całą właściwość w każdym obiekcie. Z drugiej strony jest to przykład Tightly Coupling, który jest dużym nie-nie, dopóki nie będziesz w stanie naprawdę uzasadnić przyczyny.thisjest znacznie bardziej skomplikowany, ponieważ dotyczy kontekstu. W Internecie można znaleźć wiele dobrych zasobów za darmo.

To powiedziawszy, oba sposoby są tylko narzędziami językowymi i to naprawdę zależy od ciebie i problemu, który próbujesz rozwiązać, aby wybrać to, co pasuje lepiej.

Jeśli potrzebujesz mieć właściwość odpowiednią dla każdej instancji klasy, użyj this. Jeśli potrzebujesz właściwości, aby działała tak samo w każdej instancji, użyj prototype.

Aktualizacja

Jeśli chodzi o przykładowe fragmenty, pierwszy jest przykładem Singletona , więc sensowne jest użycie go thisw ciele obiektu. Możesz także ulepszyć swój przykład, czyniąc go tak modułowym (i nie zawsze musisz go również używać this).

/* Assuming it will run in a web browser */
(function (window) {
    window.myApp = {
        ...
    }
})( window );

/* And in other pages ... */
(function (myApp) {
    myApp.Module = {
        ...
    }
})( myApp );

/* And if you prefer Encapsulation */
(function (myApp) {
    myApp.Module = {
         "foo": "Foo",
         "bar": function ( string ) {
             return string;
         },
         return {
             "foor": foo,
             "bar": bar
         }
    }
})( myApp );

Twój drugi fragment nie ma większego sensu, ponieważ najpierw go używasz, thisa później próbujesz go zhakować prototype, co nie działa, ponieważ thisma wyższy priorytet prototype. Nie jestem pewien, jakie były twoje oczekiwania wobec tego fragmentu kodu i jak działał, ale bardzo polecam, aby go przeredagować.

Aktualizacja

Aby rozwinąć thiskwestię pierwszeństwa prototype, mogę pokazać przykład i wyjaśnić, jak można to wyjaśnić, ale nie mam żadnych zewnętrznych zasobów, aby je wykonać.

Przykład jest bardzo prosty:

var myClass = function () { this.foo = "Foo"; };
myClass.prototype.foo = "nice try!";
myClass.prototype.bar = "Bar";

var obj = new myClass;
obj.foo;     // Still contains "Foo" ...
obj.bar;     // Contains "Bar" as expected

Wyjaśnienie jest, jak wiemy, thisistotne w kontekście. Więc nie powstanie, dopóki kontekst nie będzie gotowy. Kiedy kontekst jest gotowy? Podczas tworzenia nowej instancji! Resztę powinieneś odgadnąć! Oznacza to, że chociaż istnieje prototypedefinicja, ale thisbardziej sensowne jest nadanie pierwszeństwa, ponieważ chodzi o tworzenie nowej instancji w tym momencie.

53777A
źródło
To jest częściowo niesamowita odpowiedź! Czy możesz go edytować i przejść do dalszych szczegółów, porównując i kontrastując te dwie metody? Pierwsze dwa akapity wprowadzają mnie w błąd co do metody projektowania, o której mowa. Twoje ćwiczenie jest doskonałe! Nigdy nie zastanawiałem się nad siłą takiego prototypowania. Czy możesz również podać półokreślony przykład użycia przypadku, kiedy należy zastosować każdą metodę? Jeszcze raz dziękuję, to świetnie.
JDillon522
@ JDillon522 Miło to słyszeć. Spróbuję go zaktualizować w ciągu najbliższych godzin!
53777A,
@ JDillon522 Właśnie zrobiłem szybką aktualizację. Mam nadzieję, że tym razem będzie bardziej jasne.
53777A,
@ JDillon522 Nieco więcej o twoich fragmentach przykładowych ...
53777A
Niezły przykład singletona. Użyłem więc thisw głupim prototypowym przykładzie, ponieważ thisodnosi się do jego własnych właściwości, w tym metod. Nie uczę się najlepiej z czytania o kodzie, ale z patrzenia na kod. ( Kawałek MDN na ten temat , Object Playground (niesamowity) i kilka innych). Czy możesz wskazać coś, co wyjaśnia, co masz na myśli mówiąc „ thisma pierwszeństwo przed prototype”? Chciałbym przyjrzeć się temu bardziej.
JDillon522