Jakie praktyczne zastosowanie ma zamknięcie w JavaScript?


279

Dokładam wszelkich starań, aby owinąć głowę wokół zamknięć JavaScript.

Rozumiem to, zwracając funkcję wewnętrzną, będzie ona miała dostęp do dowolnej zmiennej zdefiniowanej w jej bezpośrednim obiekcie nadrzędnym.

Gdzie to by mi się przydało? Być może jeszcze nie mam wokół tego głowy. Większość przykładów, które widziałem online , nie zawiera żadnego kodu z prawdziwego świata, tylko niejasne przykłady.

Czy ktoś może mi pokazać zamknięcie?

Czy to na przykład?

var warnUser = function (msg) {
    var calledCount = 0;
    return function() {
       calledCount++;
       alert(msg + '\nYou have been warned ' + calledCount + ' times.');
    };
};

var warnForTamper = warnUser('You can not tamper with our HTML.');
warnForTamper();
warnForTamper();

17
+1 za próbę najtrudniejszego :-) Zamknięcia mogą wydawać się naprawdę zniechęcające, wiem, że były dla mnie. Po ich zrozumieniu natychmiast staniesz się znacznie lepszym programistą.
Andy E

7
Właśnie napisałem wpis na blogu o zamknięciach w JavaScript, które możesz znaleźć helfpul.
Skilldrick

@Skilldrick. link nie działa ... i ten praktyczny przykład okazał się bardzo pomocny. youtube.com/watch?v=w1s9PgtEoJs .
Abhi,

Odpowiedzi:


240

Użyłem zamknięć do robienia takich rzeczy jak:

a = (function () {
    var privatefunction = function () {
        alert('hello');
    }

    return {
        publicfunction : function () {
            privatefunction();
        }
    }
})();

Jak widać, ajest teraz obiektem z metodą publicfunction( a.publicfunction()), która wywołuje privatefunction, która istnieje tylko w zamknięciu. NIE możesz dzwonić privatefunctionbezpośrednio (tj. a.privatefunction()), Po prostu publicfunction().

To minimalny przykład, ale może widzisz zastosowania? Wykorzystaliśmy to do wymuszenia metod publicznych / prywatnych.


26
Ach, jeśli to jest zamknięcie, to użyłem zamknięć, nie wiedząc o tym! Często umieszczam funkcje w innej takiej postaci, a następnie ujawniam każdą potrzebną publicznie, zwracając dosłowny obiekt jak w twoim przykładzie.
alex

1
Tak, jak widzisz, zachowujesz kontekst funkcji, ponieważ zwracany obiekt odwołuje się do zmiennych (i funkcji). Więc ich używałeś, po prostu o tym nie wiedziałeś.
Francisco Soto

9
Technicznie każda funkcja wykonywana w JavaScript w przeglądarce jest zamknięciem, ponieważ obiekt okna jest z nią związany.
Adam Gent

9
Wiem, że to stare pytanie, ale dla mnie to wciąż nie zapewnia odpowiedniej odpowiedzi. Dlaczego nie wywołać funkcji bezpośrednio? Dlaczego potrzebujesz funkcji prywatnej?
qodeninja

5
Ponieważ chociaż przykład ma tylko funkcję, może również zawierać zmienne, które nie są dostępne z zewnątrz. Powiedz: var obj = (function () {var value = 0; return {get: function () {return value;}, set: function (val) {value = val;}}}) (); obj.set (20); obj.get (); => 20 itd.
Francisco Soto

211

Załóżmy, że chcesz policzyć, ile razy użytkownik kliknął przycisk na stronie internetowej.
W tym celu uruchamiasz funkcję przy onclickzdarzeniu przycisku, aby zaktualizować liczbę zmiennych

<button onclick="updateClickCount()">click me</button>  

Teraz może istnieć wiele podejść, takich jak:

1) Możesz użyć zmiennej globalnej i funkcji, aby zwiększyć licznik :

var counter = 0;

function updateClickCount() {
    ++counter;
    // do something with counter
}

Ale pułapką jest to, że każdy skrypt na stronie może zmienić licznik bez wywoływaniaupdateClickCount() .


2) Teraz możesz myśleć o zadeklarowaniu zmiennej wewnątrz funkcji:

function updateClickCount() {
    var counter = 0;
    ++counter;
    // do something with counter
}

Ale hej! Po każdym updateClickCount()wywołaniu funkcji licznik jest ponownie ustawiany na 1.


3) Myślisz o funkcjach zagnieżdżonych ?

Funkcje zagnieżdżone mają dostęp do zakresu „nad nimi”.
W tym przykładzie funkcja wewnętrzna updateClickCount()ma dostęp do zmiennej licznika w funkcji nadrzędnejcountWrapper()

function countWrapper() {
    var counter = 0;
    function updateClickCount() {
    ++counter;
    // do something with counter
    }
    updateClickCount();    
    return counter; 
}

Mogłoby to rozwiązać dylemat przeciwnika, gdybyś mógł dotrzeć do updateClickCount()funkcji z zewnątrz, a także znaleźć sposób na wykonaniecounter = 0 tylko raz, nie za każdym razem.


4) Zamknięcie na ratunek! (funkcja auto-wywoływania) :

 var updateClickCount=(function(){
    var counter=0;

    return function(){
     ++counter;
     // do something with counter
    }
})();

Funkcja auto-wywoływania działa tylko raz. Ustawiacounter zero (0) i zwraca wyrażenie funkcji.

Tą drogą updateClickCount staje się funkcją. „Wspaniałą” częścią jest to, że może uzyskać dostęp do licznika w zakresie nadrzędnym.

Nazywa się to zamknięciem JavaScript . Umożliwia funkcji posiadającej „ prywatna zmiennych ”.

The counterJest chronione przez zakres funkcji anonimowej, i może być zmieniona jedynie za pomocą funkcji dodawania!

Bardziej żywy przykład zamknięcia:

<script>
        var updateClickCount=(function(){
    	var counter=0;
    
    	return function(){
    	++counter;
    	 document.getElementById("spnCount").innerHTML=counter;
    	}
      })();
    </script>

    <html>
	 <button onclick="updateClickCount()">click me</button>
	  <div> you've clicked 
		<span id="spnCount"> 0 </span> times!
	 </div>
    </html>


Odniesienie: https://www.w3schools.com/js/js_function_closures.asp


50
Jest to pierwsza odpowiedź, która mnie powiedzieć: „Och, to dlaczego miałbym używać zamknięć!”
Adwokat diabła,

6
zrobiłeś mój dzień :)
JerryGoyal

15
Właśnie przeczytałem stronę w3schools na temat zamknięć, a potem przyszedłem tutaj, aby uzyskać więcej informacji. Jest to to samo, co strona w3schools
tyelford

1
@JerryGoyal, czy możesz sprawić, by działał z 2 oddzielnymi przyciskami? Nie jestem w stanie dowiedzieć się, jak to zrobić bez uciekania się do 2 zmiennych (kopii funkcji), co wydaje się usuwać niektóre z głównych korzyści / wygody.
Tyler Collier,

2
Dobra odpowiedź. Zauważ jednak, że zamknięcie nie musi być funkcją wywołującą, ale może być. Gdy zamknięcie jest wywoływane automatycznie (tj. Natychmiast wywoływane przez dodanie () po funkcji), oznacza to, że wartość zwracana jest natychmiast obliczana, a nie zwracana funkcja , a wartość zwracana jest obliczana później po wywołaniu funkcji. Zamknięcie może w rzeczywistości być dowolną funkcją w obrębie innej funkcji, a jego kluczową cechą jest to, że ma dostęp do zakresu funkcji nadrzędnej, w tym jej zmiennych i metod.
Chris Halcrow,

69

Podany przykład jest doskonały. Zamknięcia są mechanizmem abstrakcji, który pozwala bardzo dokładnie rozdzielić obawy. Twój przykład to przypadek oddzielenia instrumentacji (liczenia wywołań) od semantyki (interfejs API do zgłaszania błędów). Inne zastosowania obejmują:

  1. Przekazywanie sparametryzowanego zachowania do algorytmu (klasyczne programowanie wyższego rzędu):

    function proximity_sort(arr, midpoint) {
        arr.sort(function(a, b) { a -= midpoint; b -= midpoint; return a*a - b*b; });
    }
  2. Symulowanie programowania obiektowego:

    function counter() {
        var a = 0;
        return {
            inc: function() { ++a; },
            dec: function() { --a; },
            get: function() { return a; },
            reset: function() { a = 0; }
        }
    }
  3. Implementowanie egzotycznej kontroli przepływu, takiej jak obsługa zdarzeń jQuery i interfejsy API AJAX.


3
( int?) Ostatnio sprawdziłem, JavaScript był językiem pisanym kaczym językiem. Może myślałeś o Javie?
Hello71,

1
@ Hello71: Myślałem o JavaScript, ale stare nawyki umierają ciężko. Dobry chwyt
Marcelo Cantos,

2
@MarceloCantos wygląda na to, że zapomniałeś przecinka w implementacji licznika. Zredagowałem twój post, aby go poprawić. Mam nadzieję, że w porządku :)
Natan Streppel

2
@Streppel: Dobry połów! Cieszę się, że ulepszysz mój kod. :-)
Marcelo Cantos,

próbując zrozumieć nr 1 ... Jak nazwałbyś segment bliskości?
Dave2081,

26

Wiem, że spóźniłem się z odpowiedzią na to pytanie, ale może to pomóc każdemu, kto nadal szuka odpowiedzi w 2018 roku.

Zamknięć JavaScript można używać do implementacji funkcji przepustnicy i usuwania błędów w aplikacji.

Ograniczanie :

Ograniczanie ogranicza maksymalną liczbę wywołań funkcji w czasie. Jak w „wykonaj tę funkcję co najwyżej raz na 100 milisekund”.

Kod :

const throttle = (func, limit) => {
  let isThrottling
  return function() {
    const args = arguments
    const context = this
    if (!isThrottling) {
      func.apply(context, args)
      isThrottling = true
      setTimeout(() => isThrottling = false, limit)
    }
  }
}

Ogłaszając :

Debugowanie nakłada limit na funkcję, która nie będzie wywoływana ponownie, dopóki nie upłynie określony czas bez jej wywołania. Jak w „wykonaj tę funkcję tylko wtedy, gdy minęło 100 milisekund bez wywołania”.

Kod:

const debounce = (func, delay) => {
  let debouncing
  return function() {
    const context = this
    const args = arguments
    clearTimeout(debouncing)
    debouncing = setTimeout(() => func.apply(context, args), delay)
  }
}

Jak widać zamknięcia pomogły we wdrożeniu dwóch pięknych funkcji, które każda aplikacja internetowa powinna zapewniać płynne działanie interfejsu użytkownika.

Mam nadzieję, że to komuś pomoże.


18

Tak, to dobry przykład przydatnego zamknięcia. Wywołanie warnUser tworzy calledCountzmienną w swoim zakresie i zwraca anonimową funkcję zapisaną w warnForTamperzmiennej. Ponieważ nadal występuje zamknięcie wykorzystujące zmienną wywoływanąCount, nie jest ona usuwana po wyjściu z funkcji, więc każde wywołaniewarnForTamper() i zaalarmuje wartość.

Najczęstszym problemem, jaki widzę na StackOverflow, jest to, że ktoś chce „opóźnić” użycie zmiennej, która jest zwiększana w każdej pętli, ale ponieważ zakres zmiennej jest zmienny, każde odwołanie do zmiennej będzie po zakończeniu pętli, w wyniku czego stan końcowy zmiennej:

for (var i = 0; i < someVar.length; i++)
    window.setTimeout(function () { 
        alert("Value of i was "+i+" when this timer was set" )
    }, 10000);

Spowodowałoby to, że każdy alert pokazywałby tę samą wartość i, do której został zwiększony, gdy pętla się kończy. Rozwiązaniem jest utworzenie nowego zamknięcia, oddzielnego zakresu dla zmiennej. Można to zrobić za pomocą natychmiast wykonanej funkcji anonimowej, która odbiera zmienną i przechowuje jej stan jako argument:

for (var i = 0; i < someVar.length; i++)
    (function (i) {
        window.setTimeout(function () { 
            alert("Value of i was "+i+" when this timer was set" )
        }, 10000);
    })(i); 

Interesujące -1, myślę, że nie jest to „praktyczne zastosowanie do zamknięcia w javascript”?
Andy E

1
Znalazłem pewien użytek w czytaniu, więc przyznałem +1 przed głosowaniem negatywnym.
alex

1
@alex: dzięki, zauważyłem entuzjazm. Prawie przyzwyczaiłem się do anonimowych głosów negatywnych tutaj w SO. Denerwuje mnie to tylko dlatego, że naprawdę chciałbym wiedzieć, czy powiedziałem coś niedokładnego lub niewłaściwego, a one sprawiają, że myślisz, że zostałeś oceniony przez kogoś innego jako odpowiedź, która chce lepszej widoczności dla własnej odpowiedzi. Na szczęście nie jestem mściwym typem ;-)
Andy E

1
Myślę, że jest to bardziej obejście w przypadku zepsutego zakresu bloków JavaScript. Powinieneś po prostu móc dodać var ​​j = i; przed pierwszym setTimeout i uzyskaj alert, aby użyć tego j. Innym obejściem jest użycie „z” tak: for (var i = 0; i <someVar.length; i ++) {with ({i: i}) {window.setTimeout (function () {alert ("Wartość byłem ustawiony „+ i +”, gdy ten zegar był ustawiony ”)}, 100);}}
davidbuttar

1
@AndyE Funny może nie być właściwym słowem. Właśnie zauważyłem, że często ludzie używają funkcji auto-inwokacji do wyjaśnienia zamknięć, jak wiele odpowiedzi na tej stronie. Ale funkcja zwrotna w setTimeout jest również zamknięciem; można to uznać za „praktyczne zastosowanie”, ponieważ można uzyskać dostęp do niektórych innych zmiennych lokalnych z wywołania zwrotnego. Kiedy uczyłem się o zamknięciach, uświadomienie sobie tego było dla mnie przydatne - że zamknięcia są wszędzie, nie tylko w arkadowych wzorcach JavaScript.
antoine

14

W szczególności w języku JavaScript (lub dowolnym języku ECMAScript) zamknięcia są przydatne do ukrywania implementacji funkcjonalności, a jednocześnie ujawniają interfejs.

Wyobraź sobie na przykład, że piszesz klasę metod narzędzia daty i chcesz umożliwić użytkownikom wyszukiwanie nazw dni powszednich według indeksu, ale nie chcesz, aby mogli oni modyfikować tablicę nazw używanych pod maską.

var dateUtil = {
  weekdayShort: (function() {
    var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
    return function(x) {
      if ((x != parseInt(x)) || (x < 1) || (x > 7)) {
        throw new Error("invalid weekday number");
      }
      return days[x - 1];
    };
  }())
};

Zauważ, że daystablica może być po prostu przechowywana jako właściwość dateUtilobiektu, ale wtedy byłaby widoczna dla użytkowników skryptu, a nawet mogliby ją zmienić, gdyby chcieli, nawet nie potrzebując kodu źródłowego. Ponieważ jednak jest on zawarty w funkcji anonimowej, która zwraca funkcję wyszukiwania daty, jest dostępny tylko dla funkcji wyszukiwania, więc jest teraz odporny na manipulacje.


2
Może to zabrzmieć głupio, ale czy nie mogliby po prostu otworzyć samego pliku JavaScript i zobaczyć twojej implementacji?
itsmichaelwang,

1
@Zapurdead: tak, mogli oczywiście zobaczyć implementację, ale nie mogli zmienić implementacji (przypadkowo lub celowo) bez bezpośredniej modyfikacji kodu źródłowego. Przypuszczam, że można to porównać do chronionych członków w Javie.
maerics

6

Przeglądając to, nie widzę, jak to jest „praktyczne”, jak gdybym usunął cały return function ()...kod, nadal działa dobrze. Zamknięcie nie jest konieczne
skorek

@James_Parsons Nie można następnie przypisać ich do programów obsługi zdarzeń, tak jak zrobiono to w przykładzie.
alex

5

Innym powszechnym zastosowaniem zamknięć jest powiązanie thismetody z konkretnym obiektem, co pozwala na wywołanie go w innym miejscu (np. Jako moduł obsługi zdarzeń).

function bind(obj, method) {
    if (typeof method == 'string') {
        method = obj[method];
    }
    return function () {
        method.apply(obj, arguments);
    }
}
...
document.body.addEventListener('mousemove', bind(watcher, 'follow'), true);

Zawsze watcher.follow(evt)wywoływane jest zdarzenie myszy, które jest uruchamiane .

Zamknięcia są również istotną częścią funkcji wyższego rzędu, umożliwiając bardzo powszechny wzór przepisywania wielu podobnych funkcji jako pojedynczej funkcji wyższego rzędu poprzez parametryzowanie odmiennych części. Jako abstrakcyjny przykład

foo_a = function (...) {A a B}
foo_b = function (...) {A b B}
foo_c = function (...) {A c B}

staje się

fooer = function (x) {
    return function (...) {A x B}
}

gdzie A i B nie są jednostkami składniowymi, ale ciągami kodu źródłowego (nie literałami ciągów).

Zobacz konkretny przykład w „ Usprawnianie mojego JavaScript z funkcją ”.


5

Mam pozdrowienie, które chcę powiedzieć kilka razy. Jeśli utworzę zamknięcie, mogę po prostu wywołać tę funkcję, aby nagrać powitanie. Jeśli nie utworzę zamknięcia, muszę podawać swoje imię za każdym razem.

Bez zamknięcia ( https://jsfiddle.net/lukeschlangen/pw61qrow/3/ ):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";
  console.log(message);
}

greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Billy", "Bob");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");
greeting("Luke", "Schlangen");

Z zamknięciem ( https://jsfiddle.net/lukeschlangen/Lb5cfve9/3/ ):

function greeting(firstName, lastName) {
  var message = "Hello " + firstName + " " + lastName + "!";

  return function() {
    console.log(message);
  }
}

var greetingBilly = greeting("Billy", "Bob");
var greetingLuke = greeting("Luke", "Schlangen");

greetingBilly();
greetingBilly();
greetingBilly();
greetingLuke();
greetingLuke();
greetingLuke();

1
Nie jestem pewien, ale wciąż bez zamknięcia możesz zadzwonić jako var grretBilly = pozdrowienie („Billy”, „Bob”); i wywołaj grretBilly (); Nadal zrobiłby to samo? chociaż tworzysz zamknięcie lub nie, to inny problem, ale przekazywanie nazwy za każdym razem nie jest tutaj problemem.
user2906608

4

Jeśli nie masz pojęcia o tworzeniu instancji klasy w sensie obiektowym (tj. W celu stworzenia obiektu tej klasy), to jesteś blisko zrozumienia zamknięcia.

Pomyśl o tym w ten sposób: kiedy tworzysz dwa obiekty Person, wiesz, że zmienna elementu klasy „Name” nie jest współużytkowana między instancjami; każdy obiekt ma własną „kopię”. Podobnie, po utworzeniu zamknięcia, wolna zmienna (w powyższym przykładzie „o nazwie Count”) jest powiązana z „instancją” funkcji.

Myślę, że twój skok pojęciowy jest nieco utrudniony przez fakt, że każda funkcja / zamknięcie zwracane przez funkcję warnUser (na bok: to jest funkcja wyższego rzędu ) zamknięcie wiąże „wywoływane” z tą samą wartością początkową (0), podczas gdy często podczas tworzenia zamknięć bardziej przydatne jest przekazywanie różnych inicjatorów do funkcji wyższego rzędu, podobnie jak przekazywanie różnych wartości do konstruktora klasy.

Załóżmy więc, że gdy „wywoływane” osiągnie określoną wartość, którą chcesz zakończyć sesję użytkownika; możesz chcieć dla tego różnych wartości w zależności od tego, czy żądanie przychodzi z sieci lokalnej, czy z dużego złego Internetu (tak, to wymyślony przykład). Aby to osiągnąć, możesz przekazać różne wartości początkowe dla wywoływanej wartości do warnUser (tj. -3 lub 0?).

Częścią problemu z literaturą jest nomenklatura stosowana do ich opisu („zakres leksykalny”, „zmienne swobodne”). Nie daj się zwieść, zamknięcia są prostsze niż się wydaje ... prima facie ;-)


3

Tutaj mam jeden prosty przykład koncepcji zamknięcia, z której możemy skorzystać w naszej witrynie e-commerce lub w wielu innych. Dodaję mój link jsfiddle z przykładem. zawiera małą listę produktów składającą się z 3 produktów i jednego licznika koszyka.

Jsfiddle

//Counter clouser implemented function;
var CartCouter = function(){
	var counter = 0;
  function changeCounter(val){
  	counter += val
  }
  return {
  	increment: function(){
    	changeCounter(1);
    },
    decrement: function(){
    changeCounter(-1);
    },
    value: function(){
    return counter;
    }
  }
}

var cartCount = CartCouter();
function updateCart(){
	document.getElementById('cartcount').innerHTML = cartCount.value();
  }

var productlist = document.getElementsByClassName('item');
for(var i = 0; i< productlist.length; i++){
	productlist[i].addEventListener('click',function(){
  	if(this.className.indexOf('selected')<0){
    		this.className += " selected";
        cartCount.increment();
        updateCart();
    } else{
    	this.className = this.className.replace("selected", "");
      cartCount.decrement();
      updateCart();
    }
  })
}
.productslist{
  padding:10px;
}
ul li{
  display: inline-block;
  padding: 5px;
  border: 1px solid #ddd;
  text-align: center;
  width: 25%;
  cursor: pointer;
}
.selected{
  background-color: #7CFEF0;
  color: #333;
}
.cartdiv{
  position: relative;
  float:right;
  padding: 5px;
  box-sizing: border-box;
  border: 1px solid #f1f1f1;
}
<div>
<h3>
Practical Use of JavaScript Closure consept/private variable.
</h3>
<div class="cartdiv">
    <span id="cartcount">0</span>
</div>
<div class="productslist">
    <ul >
    <li class="item">Product 1</li>
     <li class="item">Product 2</li>
     <li class="item">Product 3</li>
    </ul>

</div>
</div>


2

Zastosowanie zamknięć:

Zamknięcia są jedną z najpotężniejszych funkcji JavaScript. JavaScript pozwala na zagnieżdżanie funkcji i zapewnia funkcji wewnętrznej pełny dostęp do wszystkich zmiennych i funkcji zdefiniowanych w funkcji zewnętrznej (i wszystkich innych zmiennych i funkcji, do których ma dostęp funkcja zewnętrzna). Jednak funkcja zewnętrzna nie ma dostępu do zmiennych i funkcji zdefiniowanych w funkcji wewnętrznej. Zapewnia to rodzaj bezpieczeństwa dla zmiennych funkcji wewnętrznej. Ponadto, ponieważ funkcja wewnętrzna ma dostęp do zakresu funkcji zewnętrznej, zmienne i funkcje zdefiniowane w funkcji zewnętrznej będą żyły dłużej niż sama funkcja zewnętrzna, jeśli funkcja wewnętrzna zdoła przetrwać poza życiem funkcji zewnętrznej.

Przykład:

<script>
var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
console.log(pet.getName());                  // Vivie

console.log(pet.setName("Oliver"));   
console.log(pet.setSex("male"));
console.log(pet.getSex());                   // male
console.log(pet.getName());                  // Oliver
</script>

W powyższym kodzie zmienna nazwy funkcji zewnętrznej jest dostępna dla funkcji wewnętrznych i nie ma innego sposobu na uzyskanie dostępu do zmiennych wewnętrznych poza funkcjami wewnętrznymi. Wewnętrzne zmienne funkcji wewnętrznej pełnią funkcję bezpiecznego magazynu funkcji wewnętrznych. Przechowują „trwałe”, ale bezpieczne dane do pracy z funkcjami wewnętrznymi. Funkcje nie muszą nawet być przypisane do zmiennej ani mieć nazwy. przeczytaj tutaj, aby poznać szczegóły


2

Podoba mi się przykład fabryki funkcji Mozilli .

function makeAdder(x) {

    return function(y) {
        return x + y;
    };
}

var addFive = makeAdder(5);

console.assert(addFive(2) === 7); 
console.assert(addFive(-5) === 0);

11
Moim zdaniem jest to przykład, który nie pomaga ludziom zrozumieć zamknięć ani tego, do czego służą. Ile razy napisałeś zamknięcie, aby zwrócić funkcję dodawania liczb, inną niż jako przykład?
Mohamad

2

Wzorzec modułu JavaScript używa zamknięć. Jego ładny wzór pozwala mieć coś podobnego do „publicznego” i „prywatnego”.

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();


1

Ten wątek ogromnie pomógł mi lepiej zrozumieć, jak działają zamknięcia. Od tego czasu przeprowadziłem własne eksperymenty i opracowałem dość prosty kod, który może pomóc innym osobom zobaczyć, w jaki sposób zamknięcia mogą być używane w praktyczny sposób i jak używać zamknięcia na różnych poziomach, aby zachować zmienne podobne do statycznych i / lub zmienne globalne bez ryzyka ich nadpisania lub pomylenia ze zmiennymi globalnymi. Pozwala to śledzić kliknięcia przycisków, zarówno na poziomie lokalnym dla każdego przycisku, jak i na poziomie globalnym, licząc każde kliknięcie przycisku, przyczyniając się do uzyskania pojedynczej liczby. Uwaga: Nie użyłem do tego żadnych zmiennych globalnych, co jest w pewnym sensie ćwiczeniem - posiadanie modułu obsługi, który można zastosować do dowolnego przycisku, który również przyczynia się do czegoś globalnie.

Proszę ekspertów, dajcie mi znać, jeśli popełniłem tutaj jakieś złe praktyki! Nadal sam się tego uczę.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Closures on button presses</title>
<script type="text/javascript">

window.addEventListener("load" , function () {
    /*
    grab the function from the first closure,
    and assign to a temporary variable 
    this will set the totalButtonCount variable
    that is used to count the total of all button clicks

    */
    var buttonHandler = buttonsCount(); 

    /*
    using the result from the first closure (a function is returned) 
    assign and run the sub closure that carries the 
    individual variable for button count and assign to the click handlers 
    */
    document.getElementById("button1").addEventListener("click" , buttonHandler() );
    document.getElementById("button2").addEventListener("click" , buttonHandler() );
    document.getElementById("button3").addEventListener("click" , buttonHandler() );

    // Now that buttonHandler has served its purpose it can be deleted if needs be
    buttonHandler = null;
});



function buttonsCount() {
    /* 
        First closure level 
        - totalButtonCount acts as a sort of global counter to count any button presses
    */
    var totalButtonCount = 0;

    return  function () {
        //second closure level
        var myButtonCount = 0;

        return function (event) {
            //actual function that is called on the button click
            event.preventDefault();
            /*  
               increment the button counts.
               myButtonCount only exists in the scope that is 
               applied to each event handler, therefore acts 
               to count each button individually whereas because 
               of the first closure totalButtonCount exists at 
               the scope just outside, so maintains a sort 
               of static or global variable state 
            */

            totalButtonCount++;
            myButtonCount++;

            /* 
                do something with the values ... fairly pointless 
                but it shows that each button contributes to both 
                it's own variable and the outer variable in the 
                first closure 
            */
            console.log("Total button clicks: "+totalButtonCount);
            console.log("This button count: "+myButtonCount);
        }
    }
}

</script>
</head>

<body>
    <a href="#" id="button1">Button 1</a>
    <a href="#" id="button2">Button 2</a>
    <a href="#" id="button3">Button 3</a>
</body>
</html>

0

Odniesienie: Praktyczne zastosowanie zamknięć

W praktyce zamknięcia mogą tworzyć eleganckie projekty, pozwalające na dostosowanie różnych obliczeń, odroczonych połączeń, oddzwaniania, tworzenia zamkniętego zakresu itp.

Przykład metody sortowania tablic, która przyjmuje jako argument funkcję warunku sortowania:

[1, 2, 3].sort(function (a, b) {
    ... // sort conditions
});

Mapowanie funkcjonałów jako metoda mapowania tablic, która mapuje nową tablicę pod warunkiem argumentu funkcjonalnego:

[1, 2, 3].map(function (element) {
   return element * 2;
}); // [2, 4, 6]

Często wygodnie jest zaimplementować funkcje wyszukiwania za pomocą argumentów funkcjonalnych określających prawie nieograniczone warunki wyszukiwania:

 someCollection.find(function (element) {
        return element.someProperty == 'searchCondition';
    });

Możemy również zauważyć zastosowanie funkcji funkcjonalnych, na przykład metody forEach, która stosuje funkcję do tablicy elementów:

[1, 2, 3].forEach(function (element) {
    if (element % 2 != 0) {
        alert(element);
    }
}); // 1, 3

Funkcja jest stosowana do argumentów (do listy argumentów - w zastosowaniu i argumentów pozycjonowanych - w wywołaniu):

(function () {
  alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

Odroczone połączenia:

var a = 10;
    setTimeout(function () {
      alert(a); // 10, after one second
    }, 1000);

Funkcje oddzwaniania:

var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
  // callback, which will be called deferral ,
  // when data will be ready;
  // variable "x" here is available,
  // regardless that context in which,
  // it was created already finished
  alert(x); // 10
};

Tworzenie enkapsulowanego zakresu w celu ukrycia obiektów pomocniczych:

var foo = {};
(function (object) {
  var x = 10;
  object.getX = function _getX() {
    return x;
  };
})(foo);
alert(foo.getX());// get closured "x" – 10

0

Znaczna część kodu, który piszemy w JavaScript, jest oparta na zdarzeniach - definiujemy pewne zachowanie, a następnie dołączamy je do zdarzenia wyzwalanego przez użytkownika (takiego jak kliknięcie lub naciśnięcie klawisza). Nasz kod jest zazwyczaj dołączany jako wywołanie zwrotne: pojedyncza funkcja, która jest wykonywana w odpowiedzi na zdarzenie. size12, size14 i size16 są teraz funkcjami, które zmienią rozmiar tekstu podstawowego odpowiednio na 12, 14 i 16 pikseli. Możemy dołączyć je do przycisków (w tym przypadku linków) w następujący sposób:

function makeSizer(size) {
    return function() {
    document.body.style.fontSize = size + 'px';
    };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

Skrzypce


Chociaż ten kod może odpowiedzieć na pytanie, zapewnienie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
Kaczor Donald

1
wydaje mi się, że ten przykład można zaimplementować bez zamykania za pomocą standardowej funkcji. Próbuję znaleźć przykład czegoś, czego NIE MOŻNA wdrożyć bez zamknięcia
Zach Smith 5'17

0

Zamknięcia są użytecznym sposobem tworzenia , sekwencja zwiększana na żądanie:

    var foobar = function(i){var count = count || i; return function(){return ++count;}}

    baz = foobar(1);
    console.log("first call: " + baz()); //2
    console.log("second call: " + baz()); //3

Różnice podsumowano w następujący sposób:

Funkcje anonimowe Funkcje zdefiniowane

Nie może być użyty jako metoda Może być użyty jako metoda obiektu

Istnieje tylko w zakresie, w którym jest zdefiniowany Istnieje w obrębie obiektu, w którym jest zdefiniowany

Może być wywołany tylko w zakresie, w którym jest zdefiniowany Może być wywołany w dowolnym punkcie kodu

Można przypisać nową wartość lub usunąć Nie można usunąć ani zmienić

Bibliografia


0

W podanej próbce wartość zawartej zmiennej „licznik” jest chroniona i można ją zmienić tylko przy użyciu podanych funkcji (przyrost, spadek). ponieważ jest w zamknięciu,

var MyCounter= function (){
    var counter=0;
    return {
    	increment:function () {return counter += 1;},
        decrement:function () {return counter -= 1;},
        get:function () {return counter;}
    };
};

var x = MyCounter();
//or
var y = MyCounter();

alert(x.get());//0
alert(x.increment());//1
alert(x.increment());//2

alert(y.increment());//1
alert(x.get());// x is still 2


0

Wyjaśnienie praktycznego zastosowania Zamknięcia w JavaScript ---

Kiedy tworzymy funkcję wewnątrz innej funkcji, tworzymy zamknięcie. Zamknięcia są potężne, ponieważ są w stanie odczytywać i przetwarzać dane jego funkcji zewnętrznych. Za każdym razem, gdy wywoływana jest funkcja, tworzony jest nowy zakres dla tego wywołania. Zmienna lokalna zadeklarowana w funkcji należy do tego zakresu i można do niej uzyskać dostęp tylko z tej funkcji. Gdy funkcja zakończy wykonywanie, zakres zwykle jest niszczony.

Prostym przykładem takiej funkcji jest:

function buildName(name) { 
    const greeting = "Hello, " + name; 
    return greeting;
}

W powyższym przykładzie funkcja buildName () deklaruje powitanie zmiennej lokalnej i zwraca ją. Każde wywołanie funkcji tworzy nowy zasięg z nową zmienną lokalną. Po zakończeniu wykonywania funkcji nie mamy możliwości ponownego odniesienia się do tego zakresu, więc są one usuwane.

Ale co powiesz, kiedy będziemy mieli link do tego zakresu?

Spójrzmy na następną funkcję:

function buildName(name) { 
    const greeting = "Hello, " + name + " Welcome "; 
    const sayName = function() {
        console.log(greeting); 
    };
    return sayName; 
}

const sayMyName = buildName("Mandeep");
sayMyName();  // Hello, Mandeep Welcome

Funkcja sayName () z tego przykładu jest zamknięciem. Funkcja sayName () ma własny zasięg lokalny (ze zmiennym powitaniem), a także ma dostęp do zasięgu funkcji zewnętrznej (zamykającej). W tym przypadku zmienne powitanie z buildName ().

Po zakończeniu wykonywania buildName zakres nie jest niszczony w tym przypadku. Funkcja sayMyName () nadal ma do niej dostęp, więc nie będzie usuwana. Jednak nie ma innego sposobu dostępu do danych z zakresu zewnętrznego poza zamknięciem. Zamknięcie służy jako brama między kontekstem globalnym a zasięgiem zewnętrznym.


0

Próbuję uczyć się klastrów i myślę, że przykład, który stworzyłem, jest praktycznym przykładem użycia. Możesz uruchomić fragment kodu i zobaczyć wynik w konsoli. mamy dwóch oddzielnych użytkowników, którzy mają osobne dane. Każdy z nich może zobaczyć aktualny stan i go zaktualizować.

function createUserWarningData(user) {
  const data = {
    name: user,
    numberOfWarnings: 0,
  };

  function addWarning() {
    data.numberOfWarnings = data.numberOfWarnings + 1;
  }

  function getUserData() {
    console.log(data);
    return data;
  }

  return {
    getUserData: getUserData,
    addWarning: addWarning,
  };
}

const user1 = createUserWarningData("Thomas");
const user2 = createUserWarningData("Alex");

//USER 1
user1.getUserData(); // returning data user object
user1.addWarning(); // add one warning to specific user
user1.getUserData(); // returning data user object

//USER2
user2.getUserData(); // returning data user object
user2.addWarning(); // add one warning to specific user
user2.addWarning(); // add one warning to specific user
user2.getUserData(); // returning data user object

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.