Jak nazywa się ten wzorzec JavaScript i dlaczego jest używany?

100

Studiuję THREE.js i zauważyłem wzorzec, w którym funkcje są zdefiniowane w następujący sposób:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Przykład patrz metoda raycast tutaj ).

Normalna zmienność w taki sposób będzie wyglądać następująco:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

Porównując pierwszą wersję z normalną odmianą, pierwsza wydaje się różnić tym:

  1. Przypisuje wynik samowykonującej się funkcji.
  2. Definiuje zmienną lokalną w ramach tej funkcji.
  3. Zwraca rzeczywistą funkcję zawierającą logikę, która korzysta ze zmiennej lokalnej.

Zatem główna różnica polega na tym, że w pierwszej wariacji słupek jest przypisywany tylko raz, podczas inicjalizacji, podczas gdy druga zmiana tworzy tę zmienną tymczasową za każdym razem, gdy jest wywoływana.

Moje najlepsze przypuszczenie, dlaczego jest to używane, jest takie, że ogranicza liczbę wystąpień paska (będzie tylko jeden), a tym samym oszczędza obciążenie związane z zarządzaniem pamięcią.

Moje pytania:

  1. Czy to założenie jest słuszne?
  2. Czy jest jakaś nazwa tego wzoru?
  3. Dlaczego to jest używane?
Patrick Klug
źródło
1
@ChrisHayes w porządku. Oznaczyłem go jako THREE.js, ponieważ uważałem, że współtwórcy THREE.js są najbardziej wykwalifikowani, aby odpowiedzieć na to pytanie, ale tak, jest to ogólne pytanie dotyczące JS.
Patrick Klug,
2
Uważam, że nazywa się to zamknięciami. Możesz o nich poczytać.
StackFlowed
1
Jeśli jest to jedyne miejsce, w którym tworzona jest instancja Bar, jest to wzorzec singleton .
Paul
8
Niekoniecznie po to, by oszczędzać pamięć, ale może zachowywać stan między wywołaniami
Juan Mendes,
2
@wrongAnswer: niezupełnie. tutaj funkcja anonimowa (która byłaby zamknięciem) jest wykonywana natychmiastowo.
njzk2

Odpowiedzi:

100

Twoje założenia są prawie poprawne. Przejrzyjmy najpierw te.

  1. Przypisuje powrót samowykonującej się funkcji

Nazywa się to wyrażeniem funkcyjnym wywołanym natychmiast lub IIFE

  1. Definiuje zmienną lokalną w ramach tej funkcji

Jest to sposób na posiadanie prywatnych pól obiektów w JavaScript, ponieważ w privateinny sposób nie zapewnia słowa kluczowego ani funkcji.

  1. Zwraca rzeczywistą funkcję zawierającą logikę, która korzysta ze zmiennej lokalnej.

Ponownie, głównym punktem jest to, że ta zmienna lokalna jest prywatna .

Czy jest jakaś nazwa tego wzoru?

AFAIK, możesz nazwać ten wzorzec wzorca modułu . Cytowanie:

Wzorzec modułu obejmuje „prywatność”, stan i organizację za pomocą domknięć. Zapewnia sposób pakowania kombinacji publicznych i prywatnych metod i zmiennych, chroniąc elementy przed wyciekiem do zakresu globalnego i przypadkową kolizją z interfejsem innego programisty. W przypadku tego wzorca zwracany jest tylko publiczny interfejs API, pozostawiając wszystko inne w zamknięciu jako prywatne.

Porównując te dwa przykłady, moje najlepsze przypuszczenia, dlaczego używany jest pierwszy z nich, to:

  1. Implementuje wzorzec projektowy Singleton.
  2. Można kontrolować sposób tworzenia obiektu określonego typu, korzystając z pierwszego przykładu. Jednym bliskim odpowiednikiem tego punktu mogą być statyczne metody fabryczne, jak opisano w Efektywna Java.
  3. Jest to wydajne, jeśli za każdym razem potrzebujesz tego samego stanu obiektu.

Ale jeśli za każdym razem potrzebujesz obiektu waniliowego, ten wzór prawdopodobnie nie doda żadnej wartości.

MD. Sahib Bin Mahboob
źródło
1
+1 za poprawne zidentyfikowanie wzoru z książki Addy Osmani. Masz rację w swoim nazewnictwie - to jest rzeczywiście wzorzec modułu - przy okazji ujawniający wzorzec modułu.
Benjamin Gruenbaum
4
Zgadzam się z twoją odpowiedzią, z wyjątkiem części „bez gotowych do użycia zmiennych prywatnych”. Wszystkie zmienne JS mają zasięg leksykalny „zaraz po wyjęciu z pudełka”, co jest potężniejszym / ogólnym mechanizmem niż zmienne „prywatne” (np. Takie, jakie można znaleźć w Javie); dlatego JS obsługuje zmienne „prywatne” jako specjalny przypadek sposobu, w jaki obsługuje wszystkie zmienne.
Warbo,
Myślę, że przez „zmienne prywatne” masz na myśli prywatne „pole obiektu”
Ian Ringrose
1
Tylko po szczegóły: klauskomenda.com/code/javascript-programming-patterns/#module
Nagaraj Tantri
4
@LukaHorvat: w rzeczywistości javascript nie jest bardziej "potężny" niż inne języki (wolę termin ekspresyjny). W rzeczywistości jest mniej wyrazisty, ponieważ jedynym sposobem ochrony zmiennych jest umieszczenie ich w funkcji, aby uniknąć wszelkich bzdur związanych z ponownym wykorzystaniem zmiennych. Wzorzec Module jest definitywnym wymogiem do stworzenia dobrego kodu javascript, ale nie jest to cecha języka, jest raczej smutnym obejściem, aby uniknąć ugryzienia przez słabości języka.
Falanwe
11

Ogranicza to koszty inicjalizacji obiektu i dodatkowo zapewnia, że ​​wszystkie wywołania funkcji używają tego samego obiektu. Pozwala to na przykład na przechowywanie stanu w obiekcie do wykorzystania w przyszłości.

Chociaż możliwe jest, że ogranicza to użycie pamięci, zwykle GC i tak zbiera nieużywane obiekty, więc ten wzorzec raczej nie pomoże.

Ten wzór jest specyficzną formą zamknięcia .

Fengyang Wang
źródło
1
W JS jest to zwykle określane jako „moduł”
Lesha Ogonkov
2
Nie nazwałbym tego „specyficzną formą zamknięcia” per se. To wzór, w którym zastosowano zamknięcie. Nazwa wzoru wciąż jest do wzięcia.
Chris Hayes,
4
Czy naprawdę potrzebuje nazwy? Czy wszystko musi być wzorem? Czy naprawdę potrzebujemy nieskończonej taksonomii wariantów „wzorca modułowego”? Czy nie może to być po prostu „IIFE z pewnymi zmiennymi lokalnymi, które zwracają funkcję?”
Dagg Nabbit,
3
@DaggNabbit Kiedy pojawia się pytanie „jak nazywa się ten wzorzec”? Tak, potrzebuje nazwy lub przekonującego argumentu, że go nie ma. Poza tym wzorce istnieją nie bez powodu. Nie rozumiem, dlaczego narzekasz na nich tutaj.
Chris Hayes,
4
@ChrisHayes, jeśli potrzebuje nazwy, po co miałbyś argumentować, że jej nie ma? To nie ma żadnego sensu. Jeśli tego potrzebuje, nie może go mieć. Nie mam problemu z wzorami, ale nie sądzę, aby konieczne było klasyfikowanie każdego prostego idiomu jako wzorca. Takie postępowanie prowadzi do myślenia w ograniczony sposób („Czy to jest wzorzec modułu? Czy używam wzorca modułu poprawnie?” W porównaniu z „Mam IIFE z pewnymi zmiennymi lokalnymi, które zwracają funkcję, czy ten projekt działa dla mnie?”)
Dagg Nabbit
8

Nie jestem pewien, czy ten wzorzec ma bardziej poprawną nazwę, ale dla mnie wygląda to na moduł, a powodem jego użycia jest zarówno hermetyzacja, jak i utrzymanie stanu.

Zamknięcie (identyfikowane przez funkcję w funkcji) zapewnia, że ​​funkcja wewnętrzna ma dostęp do zmiennych w funkcji zewnętrznej.

W podanym przykładzie funkcja wewnętrzna jest zwracana (i przypisywana do foo) przez wykonanie funkcji zewnętrznej, co oznacza, że tmpObjectnadal istnieje w zamknięciu, a wiele wywołań funkcji wewnętrznej foo()będzie działać na tym samym wystąpieniu tmpObject.

itsmejodie
źródło
5

Kluczowa różnica między twoim kodem a kodem Three.js polega na tym, że w kodzie Three.js zmienna tmpObjectjest inicjalizowana tylko raz, a następnie współdzielona przez każde wywołanie zwróconej funkcji.

Byłoby to przydatne do utrzymywania stanu między wywołaniami, podobnie jak w staticprzypadku używania zmiennych w językach C-podobnych.

tmpObject jest zmienną prywatną, widoczną tylko dla funkcji wewnętrznej.

Zmienia użycie pamięci, ale nie jest przeznaczony do oszczędzania pamięci.

mcfedr
źródło
5

Chciałbym wnieść swój wkład w ten interesujący wątek, rozszerzając koncepcję ujawniającego wzorca modułu, który zapewnia, że ​​wszystkie metody i zmienne pozostają prywatne, dopóki nie zostaną jawnie ujawnione.

wprowadź opis obrazu tutaj

W tym drugim przypadku metoda dodawania nosiłaby nazwę Calculator.add ();

carlodurso
źródło
0

W podanym przykładzie pierwszy fragment kodu będzie używał tej samej instancji tmpObject dla każdego wywołania funkcji foo (), gdzie tak jak w drugim fragmencie, tmpObject będzie za każdym razem nową instancją.

Jednym z powodów, dla których mógł zostać użyty pierwszy fragment kodu, jest to, że zmienna tmpObject może być współużytkowana między wywołaniami funkcji foo (), bez wycieku jej wartości do zakresu, w którym jest zadeklarowana foo ().

Wersja funkcji pierwszego fragmentu kodu, która nie została natychmiast wykonana, wyglądałaby tak:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Zauważ jednak, że ta wersja ma tmpObject w tym samym zakresie co foo (), więc można nim później manipulować.

Lepszym sposobem na osiągnięcie tej samej funkcjonalności byłoby użycie osobnego modułu:

Moduł „foo.js”:

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Moduł 2:

var foo = require('./foo');

Porównanie wydajności IEF i nazwanej funkcji twórcy foo: http://jsperf.com/ief-vs-named-function

Kory Nunn
źródło
3
Twój „lepszy” przykład działa tylko w NodeJS i nie wyjaśniłeś, jak jest lepszy.
Benjamin Gruenbaum
Tworzenie osobnego modułu nie jest „lepsze”, jest po prostu inne. W szczególności jest to sposób na zwinięcie funkcji wyższego rzędu do obiektów pierwszego rzędu. Kod pierwszego rzędu jest zwykle łatwiejszy do przejścia, ale generalnie jest bardziej szczegółowy i zmusza nas do ponownego określenia wyników pośrednich.
Warbo
Moduły @BenjaminGruenbaum są dostępne nie tylko w Node, istnieje wiele rozwiązań modułowych po stronie klienta, na przykład browserify. Uważam, że rozwiązanie modułowe jest „lepsze”, ponieważ jest bardziej czytelne, łatwiejsze do debugowania i zawiera więcej informacji na temat zakresu i miejsca.
Kory Nunn