Soru JavaScript kapanışları nasıl çalışır?


JavaScript kapanışlarını, içerdikleri kavramların (örneğin işlevler, değişkenler ve benzerleri) bilgisi olan birisine nasıl açıklarsınız, ancak kapanışları kendileri anlamadılar mı?

Ben gördüm Şema örneği Wikipedia'da verilen, ancak maalesef yardımcı olmadı.


7654


Menşei


Bunlarla ve benim pek çok cevabımla olan problemim, basit bir teorik perspektiften yaklaşmaktan ziyade, Javascript'te neden kapatmanın gerekli olduğunu ve bunları kullandığınız pratik durumları açıklamaktan ziyade onlara yaklaşmaktır. Her zaman düşündüğünüz, "ama neden?" Diye yazmanız gereken bir makale. Basitçe başlayacağım: kapanışlar, aşağıdaki iki gerçek JavaScript ile başa çıkmak için düzgün bir yoldur: a. kapsam, fonksiyon seviyesindedir, blok seviyesi değil, b. JavaScript'te yaptığınız şeylerin çoğu asenkron / olay odaklı. - Jeremy Burton
@Redsandro Birincisi, olay odaklı kod yazmayı çok daha kolay hale getirir. Sayfa HTML ile ilgili özellikleri veya kullanılabilir özellikleri belirlemek için yüklendiğinde bir işlevi tetikleyebilir. Bu işleve bir işleyici tanımlayabilir ve kurabilirim ve işleyici çağrıldığında her yeniden sorgulama yapmak zorunda kalmadan tüm içerik bilgisine sahip olabilirim. Sorunu bir kez çözün, işleyicinin yeniden başlatılmasında azaltılmış ek yük ile bu işleyicinin gerekli olduğu her sayfada yeniden kullanın. Aynı verilerin, iki kez sahip olmayan bir dilde yeniden eşlendiğini gördünüz mü? Kapanışlar bu tür şeylerden kaçınmak için çok daha kolay. - Erik Reppen
Java programcıları için kısa cevap, bir iç sınıfın işlev eşdeğeri olmasıdır. Bir iç sınıf ayrıca dış sınıfın bir örneğine örtük bir işaretçi tutar ve aynı amaç için kullanılır (yani olay işleyicileri yaratır). - Boris van Schooten
Buradan daha iyi anladım: javascriptissexy.com/understand-javascript-closures-with-ease. Hala gerekli bir kapatma Diğer cevapları okuduktan sonra kapanıyor. :) - Akhoy
Bu kullanışlı örneği çok yararlı buldum: youtube.com/watch?v=w1s9PgtEoJs - Abhi


Cevaplar:


Yeni başlayanlar için JavaScript kapanışları

Morris, Tue, 2006-02-21 10:19 tarafından sunulmuştur. Topluluktan beri düzenlenmiş.

Kapanışlar büyü değil

Bu sayfa, bir programcının bunları anlayabilmesi için çalışan JavaScript kodunu kullanarak kapanmaları açıklar. Gurular ya da fonksiyonel programcılar için değil.

Kapanışlar zor değil çekirdek kavramın bir kez kavrandığını anlamak. Ancak, onlar hakkında herhangi bir akademik makale veya akademik yönelimli bilgiler okuyarak anlamak imkansızdır!

Bu makale, ana akım dilinde bazı programlama deneyimlerine sahip programcılara yöneliktir ve aşağıdaki JavaScript işlevini okuyabilmektedir:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Bir kapanış örneği

İki cümle özeti:

  • Bir kapatma, desteklemenin bir yoludur birinci sınıf fonksiyonlar; değişkenleri kapsam dahilinde (ilk ilan edildiğinde), değişkene atanabilir, bir işleve argüman olarak geçirilebilir veya işlev sonucu olarak döndürebilen bir ifadedir.

  • Veya bir kapatma, bir işlev yürütülmeye başladığında ayrılan bir yığın çerçevedir ve serbest değil işlev döndükten sonra (yığına yığın yerine bir 'yığın çerçevesi' atanmış gibi!).

Aşağıdaki kod bir işleve bir başvuru döndürür:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

Çoğu JavaScript programcısı, bir fonksiyona bir referansın bir değişkene nasıl döndüğünü anlayacaktır (say2Yukarıdaki kodda. Eğer yapmazsanız, kapanışları öğrenebilmeniz için önce buna bakmanız gerekir. C kullanan bir programcı, işlevi bir işleve bir gösterici döndürme işlevi olarak düşünür ve değişkenler say ve say2 her biri bir işlevin bir göstergesiydi.

Bir işlevin C işaretçisi ile bir işlevin JavaScript referansı arasında kritik bir fark vardır. JavaScript'te, bir fonksiyon referans değişkenini bir işleve hem bir işaretçi olarak düşünebilirsiniz. de Bir kapanış için gizli bir işaretçi olarak.

Yukarıdaki kod bir anonim işlevi olduğu için bir kapanışa sahiptir function() { console.log(text); } beyan edildi içeride başka bir işlev sayHello2() bu örnekte. JavaScript’te function Başka bir işlevin içinde anahtar kelime, bir kapanış oluşturuyorsunuz.

C ve diğer birçok ortak dilde, sonra Bir işlev döndüğünde, yığın çerçevesi yok edildiğinden tüm yerel değişkenlere artık erişilemez.

JavaScript'te, başka bir işlev içinde bir işlev bildirirseniz, yerel değişkenler aradığınız işlevden döndükten sonra erişilebilir kalır. Bu, yukarıda gösterilmiştir çünkü fonksiyonu çağırıyoruz say2()biz döndükten sonra sayHello2(). Aradığımız kodun değişkene başvurduğuna dikkat edin text, hangi oldu yerel değişken işlev sayHello2().

function() { console.log(text); } // Output of say2.toString();

Çıktısına bakarak say2.toString()kodun değişkeni ifade ettiğini görebiliriz text. Anonim işlev referans olabilir text değeri tutan 'Hello Bob' çünkü yerel değişkenler sayHello2() bir kapatma içinde tutulur.

Bu sihir, JavaScript'te bir işlev referansının, oluşturulduğu kapağın gizli bir referansı da vardır - delegelerin bir yöntem işaretçisi ve bir nesneye yönelik gizli bir referansı olduğu gibi.

Daha fazla örnek

Bazı nedenlerden dolayı, kapanışlar, onları okurken anlamayı gerçekten zor görünüyor, ancak bazı örnekler gördüğünüzde, nasıl çalıştıklarını netleştiriyor (bir süre beni aldı). Nasıl çalıştığını anlayana kadar örnekler üzerinde çalışmanızı tavsiye ederim. Nasıl çalıştıklarını tam olarak anlamadan kapanışları kullanmaya başlarsanız, yakında çok tuhaf hatalar yaratırsınız!

Örnek 3

Bu örnek, yerel değişkenlerin kopyalanmadığını gösterir - referans olarak tutulur. Dış fonksiyon çıktığında, bir yığın çerçeveyi bellekte tutmak gibi bir şeydir!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Örnek 4

Üç global fonksiyonun ortak bir referansı vardır. aynı kapatma çünkü hepsi tek bir çağrıyla bildirildi setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Üç işlev aynı kapamaya erişim paylaştı - yerel değişkenler setupSomeGlobals() Üç fonksiyon tanımlandığında.

Yukarıdaki örnekte, eğer çağırırsanız setupSomeGlobals() tekrar, daha sonra yeni bir kapatma (stack-frame!) yaratılır. Yaşlı gLogNumber, gIncreaseNumber, gSetNumber değişkenlerin üzerine yazılır yeni Yeni kapanışa sahip fonksiyonlar. (JavaScript'te, başka bir işlev içinde bir işlev bildirdiğinizde, iç işlevler yeniden oluşturulur / yeniden oluşturulur her zaman dış fonksiyon denir.)

Örnek 5

Bu örnek, kapağın, çıkarılmadan önce dış işlevin içinde bildirilen yerel değişkenleri içerdiğini gösterir. Değişkeni unutmayın alice aslında anonim işlevinden sonra bildirilir. Anonim işlev önce bildirilir; ve bu fonksiyon çağrıldığında alice değişken çünkü alice aynı kapsamda (JavaScript yapar değişken kaldırma). Ayrıca sayAlice()() Sadece doğrudan döndürülen işlev referansını çağırır sayAlice() - Önceden yapıldığı gibi geçici değişken olmadan aynıdır.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: Ayrıca not edin say Değişken de kapağın içinde, ve içinde bildirilen başka bir işlev tarafından erişilebilir sayAlice()veya iç işlev içinde yinelemeli olarak erişilebilir.

Örnek 6

Bu bir çok insan için gerçek bir tuhaflık, yani anlaman gerek. Bir döngü içinde bir işlevi tanımladığınızda çok dikkatli olun: kapağın yerel değişkenleri ilk düşünebildiğiniz gibi davranmayabilir.

Bu örneği anlamak için Javascript'teki "değişken kaldırma" özelliğini anlamalısınız.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

Çizgi result.push( function() {console.log(item + ' ' + list[i])} Sonuç dizisine üç kez bir anonim işlev için bir başvuru ekler. Anonim işlevlere çok aşina değilseniz, böyle düşünün:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Örneği çalıştırdığınızda şunu unutmayın. "item2 undefined" üç kez kaydedilir! Bunun nedeni, önceki örneklerde olduğu gibi, yerel değişkenler için yalnızca bir tane kapatma yapılmasıdır. buildList (hangileri result, i ve item). Anonim işlevler satırda çağrıldığında fnlist[j](); hepsi aynı tek kapatmayı kullanırlar ve şu anki değeri kullanırlar i ve item o bir kapanış içinde i bir değeri var 3 çünkü döngü tamamlanmıştı ve item bir değeri var 'item2'). 0'dan beri endeksleniyoruz item bir değeri var item2. Ve i ++ artacak i değere 3.

Değişkenin blok düzeyinde bir beyanı olduğunda ne olacağını görmek faydalı olabilir. item kullanılır ( let anahtar kelime) yerine işlev kapsamı değişken bildirimi yerine var Anahtar kelime. Bu değişiklik yapılırsa, dizideki her adsız işlev result kendi kapanışına sahiptir; Örnek çalıştırıldığında çıktı aşağıdaki gibidir:

item0 undefined
item1 undefined
item2 undefined

Değişken ise i ayrıca kullanılarak tanımlanır let yerine varDaha sonra çıktı:

item0 1
item1 2
item2 3

Örnek 7

Bu son örnekte, ana işleve yapılan her çağrı ayrı bir kapanış oluşturur.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

özet

Eğer her şey tamamen belirsiz görünüyorsa, yapılacak en iyi şey örneklerle oynamaktır. Bir açıklama okumak, örnekleri anlamaktan çok daha zordur. Kapaklar ve istifler, vb. İle ilgili açıklamalarım teknik olarak doğru değil - anlamaya yardımcı olacak büyük basitliklerdir. Temel fikir tokuşturulduktan sonra ayrıntıları daha sonra alabilirsiniz.

Son noktalar:

  • Ne zaman kullanırsın function başka bir işlev içinde, bir kapatma kullanılır.
  • Ne zaman kullanırsın eval() Bir işlev içinde bir kapatma kullanılır. Metin sen eval fonksiyonun yerel değişkenlerine referans verebilir ve eval kullanarak yeni yerel değişkenler bile oluşturabilirsiniz eval('var foo = …')
  • Kullandığında new Function(…) ( İşlev kurucu) bir fonksiyonun içinde bir kapanış oluşturmaz. (Yeni işlev, dış işlevin yerel değişkenlerine başvuruda bulunamaz.)
  • JavaScript'teki bir kapanma, bir işlevden çıkıldığında olduğu gibi, tüm yerel değişkenlerin bir kopyasını tutmak gibidir.
  • Bir kapamanın her zaman bir işleve yalnızca bir giriş oluşturulduğunu ve bu değişkene yerel değişkenlerin eklendiğini düşünmek en iyisidir.
  • Yeni bir yerel değişkenler kümesi her kapatıldığında bir işlev çağrılır (fonksiyonun içinde bir işlev beyanı içerdiği ve bu iç işleve bir gönderme döndüğü veya bir şekilde bir dış referansın tutulduğu dikkate alındığında) ).
  • İki işlev aynı kaynak metne sahip gibi görünebilir, ancak 'gizli' kapatmalarından dolayı tamamen farklı davranışlara sahiptir. JavaScript kodunun aslında bir işlev başvurusunun kapanmasına sahip olup olmadığını öğrenemediğini sanmıyorum.
  • Dinamik kaynak kodu değişiklikleri yapmaya çalışıyorsanız (örneğin: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), eğer işe yaramazsa myFunction bir kapanış (tabii ki, çalışma zamanında kaynak kod dizisi değiştirme yapmayı düşünmüyorsunuz, ama ...).
  • Fonksiyon bildirimleri içerisinde fonksiyon açıklamaları almak mümkündür - ve birden fazla seviyeden kapatabilirsiniz.
  • Normalde bir kapanışın, hem yakalanan değişkenlerle birlikte hem fonksiyon için bir terim olduğunu düşünüyorum. Bu makalede bu tanımı kullanmıyorum!
  • JavaScript'teki kapanışların, normal olarak işlevsel dillerde bulunanlardan farklı olduğundan şüpheleniyorum.

Bağlantılar

Teşekkürler

Eğer varsa sadece öğrenilen kapanışlar (burada veya başka bir yerde!), sonra bu yazıyı daha açık bir hale getirebileceğini düşündüğünüz herhangi bir değişiklik hakkında sizden geri bildirim almak istiyorum. Morrisjohns.com'a (morris_closure @) bir e-posta gönderin. Lütfen JavaScript’te veya kapanışta bir guru olmadığımı unutmayın.


Morris tarafından özgün yazı bulunabilir İnternet Arşivi.


6173



Parlak. Özellikle şunu seviyorum: "JavaScript'teki bir kapanış, bir işlevden çıkıldığında olduğu gibi, tüm yerel değişkenlerin bir kopyasını tutmak gibidir." - e-satis
@ e-satis - Göründüğü kadar parlak, "tüm yerel değişkenlerin bir kopyası, fonksiyondan çıkıldığında olduğu gibi" yanıltıcıdır. Değişkenlerin değerlerinin kopyalandığını, ancak gerçekten de fonksiyon çağrıldıktan sonra değişmeyen değişkenlerin kendileri olduğunu belirtir ('eval' hariç): blog.rakeshpai.me/2008/10/...). Kapanış oluşturulmadan önce işlevin dönmesi gerektiğini, ancak kapatmanın kapanma olarak kullanılabilmesinden önce geri dönmemesi gerektiğini önerir. - dlaliberte
Bu kulağa hoş geliyor: "JavaScript'te bir kapatma, tüm işlevlerin bir kopyasını tutmak gibi bir işlevden çıkmış gibi." Ama birkaç nedenden dolayı yanıltıcıdır. (1) Kapatma oluşturmak için işlev çağrısının çıkması gerekmez. (2) Bu bir kopyası değildir. değerler yerel değişkenlerin değişkenleri ama değişkenler. (3) Bu değişkenlere kimin erişimi olduğunu söylemez. - dlaliberte
Örnek 5, kodun amaçlandığı gibi çalışmadığı bir "yakalama" gösterir. Ama nasıl düzeltileceğini göstermiyor. Bu diğer cevap bunu yapmanın bir yolunu gösterir. - Matt
Bu yazının "Closures Are Not Magic" (Büyük Harfler Büyü Değildir) adlı büyük harfli harflerle başladığını ve ilk örneğini "Sihirbazda bir JavaScript referansının da oluşturulduğu kapağın gizli bir referansı olduğu" ile bitirdiğini seviyorum. - Andrew Macheret


Bir başka fonksiyonda fonksiyon anahtar kelimesini gördüğünüzde, iç fonksiyonun dış fonksiyondaki değişkenlere erişimi vardır.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Bu her zaman 16 kayıt olacak, çünkü bar erişebilir x bir argüman olarak tanımlanan foove ayrıca erişebilir tmp itibaren foo.

o olduğu bir kapatma. Bir işlev zorunda değil dönüş Bir kapanma çağrılabilmek için. Hemen sözcüksel kapsamınızın dışındaki değişkenlere erişmek bir kapatma oluşturur.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

Yukarıdaki işlev aynı zamanda 16, bar hala başvurabilir x ve tmpArtık doğrudan kapsam içinde olmasa bile.

Ancak, tmp hala içeride asılı barkapanması, ayrıca artırılıyor. Her arama yaptığınızda artar bar.

Kapanmanın en basit örneği şudur:

var a = 10;
function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Bir JavaScript işlevi çağrıldığında, yeni bir yürütme içeriği oluşturulur. Fonksiyon argümanları ve ana nesne ile birlikte, bu yürütme bağlamı, bunun dışında bildirilen tüm değişkenleri de alır (yukarıdaki örnekte, hem "hem" hem de "b").

Bir listeyi döndürerek veya bunları global değişkenlere ayarlayarak birden fazla kapatma işlevi oluşturmak mümkündür. Bunların hepsi aynı  x ve aynı tmpkendi kopyalarını yapmıyorlar.

Burada sayı x bir sayıdır. JavaScript’teki diğer editörlerde olduğu gibi foo numara denir x olduğu kopyalanan içine foo argümanı olarak x.

Öte yandan, JavaScript, nesnelerle uğraşırken her zaman referansları kullanır. Eğer diyorsan foobir nesne ile, döndürdüğü kapanış referans o orijinal nesne!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Beklendiği gibi, her çağrı bar(10) artırılacak x.memb. Beklenmeyebilir, bu x basitçe aynı nesneye atıfta bulunur age değişken! Birkaç aramadan sonra bar, age.memb 2 olacak! Bu referans, HTML nesnelerle bellek sızıntılarının temelidir.


3808



@feeela: Evet, her JS işlevi bir kapatma oluşturur. Başvurulan olmayan değişkenler, modern JS motorlarında çöp toplama için uygun olabilir, ancak bir yürütme bağlamı oluşturduğunuzda, bu bağlamın yürütme bağlamı ve değişkenleri için bir başvurusu olduğu gerçeğini değiştirmez ve Bu işlev, o orijinal referansı koruyarak farklı bir değişken kapsamına yeniden yerleştirilme potansiyeline sahip bir nesnedir. Bu kapanış.
@Ali Ben sadece sağladım jsFiddle aslında bir şey kanıtlamadım, çünkü delete başarısız olur. Bununla birlikte, fonksiyonun tanımlandığı deyim yürütüldüğünde, işlevin [[Scope]] (ve sonuç olarak çağrıldığında kendi sözcüksel ortamı için bir temel olarak kullanılması) olarak taşınacağı sözcüksel ortam belirlenir. Bu, fonksiyonun anlamı olduğu Gerçekte hangi değerlere başvurduğuna ve kapsam dışına çıkıp çıkmadığına bakılmaksızın yürütme kapsamının ENTIRE içeriğinin kapatılması. Lütfen bölüm 13.2 ve 10'a bakınız. spec - Asad Saeeduddin
İlkel türleri ve referansları açıklamaya çalışıncaya kadar bu iyi bir cevaptı. Bu tamamen yanlış olur ve kopyalanan edebi parçalardan bahseder, ki gerçekten hiçbir şeyle ilgisi yoktur. - Ry-♦
Kapanışlar, JavaScript'in sınıf tabanlı, nesne yönelimli programlamaya cevabıdır. JS sınıf temelli değildir, dolayısıyla başka bir şekilde uygulanamayan bazı şeyleri uygulamak için başka bir yol bulmak zorundaydı. - Barth Zalewski
Bu kabul edilen cevap olmalı. Sihir, içsel işlevde asla olmaz. Dış işlevi bir değişkene atadığında olur. Bu iç işlev için yeni bir yürütme içeriği oluşturur, bu nedenle "özel değişken" biriktirilebilir. Elbette, atanan dış fonksiyonun içeriği koruduğu için değişebilir. İlk cevap, orada gerçekten ne olduğunu açıklamaksızın her şeyi daha karmaşık hale getiriyor. - Albert Gao


ÖNSÖZ: soru şu olduğunda bu cevap yazılmıştır:

Yaşlı Albert'in dediği gibi: “Eğer altı yaşındaki bir çocuğa bunu açıklayamazsan, bunu gerçekten anlamıyorsun.” 27 yaşındaki bir arkadaşa JS kapaklarını açıklamaya çalıştım ve tamamen başarısız oldum.

6 kişi olduğumu ve bu konuya garip bir şekilde ilgi duyduğumu düşünen var mı?

İlk soruya tam anlamıyla girmeyi deneyen tek kişi olduğumdan eminim. O zamandan beri, soru birkaç kez mutasyona uğradı, bu yüzden cevabım artık inanılmaz derecede aptalca ve yerinde görünmeyebilir. Umarım hikayenin genel fikri bazıları için eğlenceli kalır.


Ben zor kavramları açıklarken, analoji ve metaforun büyük bir hayranıyım, o yüzden elimi bir hikaye ile deneyelim.

Bir Zamanlar:

Bir prenses vardı ...

function princess() {

Maceralarla dolu harika bir dünyada yaşıyordu. Prens Charming ile tanıştı, dünyasını tek boynuzlu at, savaşan ejderhalar, konuşan hayvanlar ve daha birçok fantastik şeyle karşılaştı.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Ama her zaman, sıkıcı iş dünyasına ve yetişkinlere geri dönmek zorunda kalacaktı.

    return {

Ve sık sık onlara bir prenses olarak son inanılmaz macerasını anlatırdı.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Ama görecekleri küçük bir kız ...

var littleGirl = princess();

... sihir ve fantezi hakkında hikayeler anlatıyor.

littleGirl.story();

Ve yetişkinler gerçek prensesleri tanıyor olsalar bile, asla bir zamanlar göremedikleri için tek boynuzlu atlara veya ejderhalara inanmazlardı. Yetişkinler, sadece küçük kızın hayal gücünün içinde var olduklarını söyledi.

Ama gerçek gerçeği biliyoruz; prensesin içindeki küçük kız ...

... içeride küçük bir kızı olan bir prenses.


2253



Bu açıklamayı gerçekten seviyorum. Okuyan ve takip etmeyenler için benzetme şudur: prenses () işlevi, özel verileri içeren karmaşık bir kapsamdır. İşlev dışında, özel veriler görülemez veya erişilemez. Prenses hayallerindeki (özel veri) tek boynuzlu atları, ejderhaları, maceraları vb. Tutar ve yetişkinler onları kendileri için göremezler. ANCAK prensesin hayal gücü için kapanışta yakalandı story() tek arabirim olan işlev littleGirl Örnek sihir dünyasına açılır. - Patrick M
Yani burada story kapanış ama kod vardı var story = function() {}; return story; sonra littleGirl kapanış olurdu. En azından aldığım izlenim bu MDN'nin kapaklı 'özel' yöntemler kullanımı: "Bu üç kamu işlevi aynı ortamı paylaşan kapaklar." - icc97
@ icc97, evet, story kapsamında sağlanan çevreye atıfta bulunan bir kapatmadır. princess. princess başka bir ima edilen kapatma, yani princess ve littleGirl herhangi bir referansı bir parents çevrede / ortamda bulunan bir dizi littleGirl var ve princess tanımlanmış. - Jacob Swartwood
@BenjaminKrupp Vücudunda daha fazla operasyon olduğunu göstermek / göstermek için açık bir kod yorumu ekledim princess yazılandan daha fazla. Ne yazık ki bu hikaye şu anda bu konuya biraz yer yok. Asıl soru şu soruyu soruyordu: “5 yıl öncesine ait JavaScript kapanışlarını açıklamak”; Benim yanıtım bile bunu yapmaya çalışan tek kişiydi. Bunun sefil bir şekilde başarısız olacağından şüphe etmemekle birlikte, en azından bu yanıt 5 yıllık bir ilgiyi koruma şansına sahip olabilirdi. - Jacob Swartwood
Aslında bana göre bu mükemmel bir anlam ifade etti. Ve itiraf etmeliyim ki sonunda bir prensesin hikayelerini ve maceralarını kullanarak bir JS kapanışını anlamak beni biraz tuhaf hissettiriyor. - Crystallize


Soruyu ciddiye alarak, tipik bir 6 yaşındaki çocuğun bilişsel olarak ne yapabildiğini öğrenmeliyiz, buna rağmen JavaScript ile ilgilenen kişi bu kadar tipik değildir.

üzerinde Çocukluk Gelişimi: 5-7 Yıl  diyor ki:

Çocuğunuz iki adımlı talimatları izleyebilecek. Örneğin, çocuğunuza "Mutfağa git ve bana bir çöp torbası ver" derseniz, bu yönü hatırlayabileceklerdir.

Bu örneği aşağıdaki gibi kapanmaları açıklamak için kullanabiliriz:

Mutfak, yerel bir değişkene sahip olan bir kapanmadır. trashBags. Mutfağın içinde bir fonksiyon var getTrashBag Bu bir çöp torbası alır ve geri verir.

Bunu JavaScript’te şöyle kodlayabiliriz:

function makeKitchen () {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

kitchen.getTrashBag(); // returns trash bag C
kitchen.getTrashBag(); // returns trash bag B
kitchen.getTrashBag(); // returns trash bag A

Kapanışların neden ilginç olduğunu açıklayan diğer noktalar:

  • Her zaman makeKitchen() denir, kendi ile ayrı bir yeni kapanış yaratılır trashBags.
  • trashBags değişken her mutfağın içindedir ve dışarıdan erişilemez, ancak içteki işlev getTrashBagmülkiyet erişim hakkına sahiptir.
  • Her işlev çağrısı bir kapanma oluşturur, ancak kapağın iç kısmına erişimi olan bir iç işlev kapağın dışından çağrılmayacaksa, kapağın etrafında tutulmasına gerek yoktur. Nesneyi ile döndürme getTrashBag Bu işlevi burada yapar.

693



Aslında, karışık bir şekilde, makeKitchen işlevi aramak gerçek kapanış, döndüğü mutfak nesnesi değil. - dlaliberte
Diğerlerinden geçerek, bu cevabı neden kapattığını ve neden kapattığını açıklamanın en kolay yolu olarak buldum. - Chetabahana
Çok fazla menü ve meze, yeterli et ve patates yok. Bu cevabı sadece aşağıdaki gibi bir kısa cümle ile geliştirebilirsiniz: "Bir işlev, sınıfların sağladığı herhangi bir kapsam belirleme mekanizmasının yokluğu için, bir işlevin kapalı bağlamıdır." - Jacob


Saman Adamı

Bir düğmeye kaç kez tıklandığınızı ve her üç tıklamada bir şeyler yapmam gerektiğini bilmem gerekiyor ...

Oldukça Açık Çözüm

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Şimdi bu işe yarayacak, ancak tek amacı sayımı takip etmek olan bir değişkenin eklenmesiyle dış kapsam içine giriyor. Bazı durumlarda, dış uygulamanızın bu bilgilere erişmesi gerekebileceğinden, bu tercih edilir. Ancak bu durumda, yalnızca her üçüncü tıklamanın davranışını değiştiriyoruz, bu yüzden tercih edilir. Bu işlevsellik olay işleyicisinin içine alın.

Bu seçeneği düşünün

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Burada birkaç şey dikkat edin.

Yukarıdaki örnekte, JavaScript'in kapatma davranışını kullanıyorum. Bu davranış, herhangi bir işlevin, oluşturulduğu kapsamı süresiz olarak erişmesine izin verir. Bunu pratik olarak uygulamak için hemen başka bir işlevi döndüren bir işlevi çağırıyorum ve döndüğüm işlevin dahili sayı değişkenine erişebildiğinden (yukarıda açıklanan kapatma davranışı nedeniyle), sonuçta kullanım için özel bir kapsam elde edilir. fonksiyon ... Çok basit değil mi? Onu sulandırırız ...

Basit bir tek satırlık kapatma

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Döndürülen işlev dışındaki tüm değişkenler döndürülen işlev için kullanılabilir, ancak bunlar döndürülen işlev nesnesine doğrudan erişilmez ...

func();  // Alerts "val"
func.a;  // Undefined

Anla? Dolayısıyla, birincil örneğimizde, sayı değişkeni, kapanışın içinde yer alır ve olay işleyicisine her zaman erişilebilir durumdadır, böylece durumunu tıklamadan tıklamaya kadar korur.

Ayrıca, bu özel değişken durum tamamen Her iki okuma için ve özel kapsam değişkenlerine atanabilir.

İşte gidiyorsun; Artık bu davranışı tamamen kaplıyorsunuz.

Tam Blog Gönderi (jQuery hususları dahil)


527



Kapağın ne olduğuna dair tanımınıza katılmıyorum. Kendini çağırması gereken bir sebep yok. Aynı zamanda "geri" olmak zorunda olduğunu söylemek için biraz basit (ve yanlış) (bu soruya verilen en iyi cevabın yorumlarında bu konuyla ilgili birçok tartışma) - James Montagne
@James oyununa katılmasanız bile, onun örneği (ve yazının tamamı) gördüğüm en iyilerinden biri. Soru benim için yaşlı ve çözülmese de, tamamen bir + 1 hak ediyor. - e-satis
"Bir düğmenin kaç kez tıklandığını bilmem ve her üç tıklamada bir şeyler yapmam gerek ..." Bu benim dikkatimi çekti. Bir kullanım vakası ve bir kapanışın nasıl gizemli bir şey olmadığını ve birçoğumuzun bunları yazdığını ancak resmi adı tam olarak bilmediğini gösteren çözüm. - Chris22
Güzel örnek, 2. örnekte "sayım" ın "sayım" değerini koruduğunu ve "eleman" her tıklandığında 0'a sıfırlanmadığını gösterdiği için. Çok bilgilendirici! - Adam
İçin +1 kapanış davranışı. Sınırlayabilir miyiz kapanış davranışı için fonksiyonlar Javascript veya bu kavram dilin diğer yapılarına da uygulanabilir mi? - Dziamid


Kapanışların açıklanması zordur, çünkü herkesin zaten çalışmayı umduğunu umduğu bazı davranışlar için kullanılırlar. Onları açıklamanın en iyi yolunu buluyorum. ben Yaptıklarını öğrendiler), onlarsız durumları hayal etmek:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

JavaScript’te ne olurdu? vermedi kapanışları biliyor musunuz? Sadece son satırdaki çağrıyı, yöntem gövdesiyle değiştirin (temelde hangi işlev çağrılarının yapıldığı) ve şunları elde edersiniz:

console.log(x + 3);

Şimdi, tanımı nerede x? Mevcut kapsamda tanımlamadık. Tek çözüm izin vermektir plus5  Taşımak kapsamı (veya daha doğrusu, ebeveynin kapsamı). Bu yoldan, x iyi tanımlanmış ve 5 değerine bağlı.


445



Katılıyorum. Fonksiyonları geleneksel "foobar" olanlar yerine anlamlı isimlere vermek de bana çok yardımcı oluyor. Semantikler sayılır. - Ishmael
Yani bir sözde dilde, temelde alert(x+3, where x = 5). where x = 5 kapanış. Ben haklı mıyım - Jus12
@ Jus12: tam olarak. Sahnelerin ardında, bir kapak, geçerli değişken değerlerin (“bağlamalar”) örneğinizde olduğu gibi depolandığı bir alan. - Konrad Rudolph
Bu tam olarak birçok insanı yanlış düşünen bir örnek. değerler döndürülen işlevde kullanılan, değişken değişkenin kendisi değil. Eğer "x + = y" döndürdüyse, ya da daha iyisi, "x * = y" ve diğer bir işlevle değiştirildiyse, hiçbir şeyin kopyalanmayacağı açıktır. Çerçeveleri yığmak için kullanılan kişiler için, bunun yerine işlevler döndükten sonra da devam edebilen yığın kareleri kullanmayı hayal edin. - Matt
@Matt ben katılmıyorum. Bir örnek değil Tüm özellikleri kapsamlı bir şekilde belgelemek gerekiyordu. Olması gerekir indirgeyici ve bir kavramın göze çarpan özelliğini gösterir. OP basit bir açıklama istedi (“altı yaşında”). Kabul edilen cevabı al: Tamamen başarısız kesin bir açıklama sunarken, tam olarak kapsamlı olmaya çalıştığı için. (Size katılıyorum ki, bu değer, bağlayıcıdan ziyade değere göre değil, JavaScript'in önemli bir özelliğidir… ama yine de, başarılı bir açıklama, çıplak asgariye indirgeyen bir açıklamadır.) - Konrad Rudolph


Bu, diğer cevapların bazılarında görünen kapanışlarla ilgili (olası) yanlış anlamaları ortadan kaldırmaya yönelik bir girişimdir.

  • Bir kapatma, yalnızca bir iç işlevi döndürdüğünüzde yaratılmaz. Aslında, kapanış işlevi hiç geri dönmek zorunda değil kapanması için. Bunun yerine, içsel işlevinizi bir dış kapsamdaki bir değişkene atayabilir veya hemen veya herhangi bir zamanda çağrılabileceği başka bir işleve argüman olarak iletebilirsiniz. Bu nedenle, kapatma işlevinin kapatılması muhtemelen oluşturulur çevreleyen fonksiyon çağrıldığında İç işlev çağrıldığında, iç işlev geri döndükten önce veya sonra herhangi bir iç işlev bu kapanmaya erişebildiğinden.
  • Bir kapanış, bir kopyasına referans vermez eski değerler değişkenlerinin kapsamı. Değişkenler kendileri kapağın bir parçasıdır ve bu değişkenlerden birine erişirken görülen değer, erişildiği anda en son değerdir. Bu nedenle, döngüler içinde yaratılan iç işlevler zor olabilir, çünkü her biri işlevin oluşturulduğu veya çağrıldığı zaman değişkenlerin bir kopyasını kapmak yerine aynı dış değişkenlere erişebilir.
  • Kapatmadaki "değişkenler", adlandırılmış işlevleri içerir işlev içinde bildirildi. Ayrıca işlevin argümanlarını içerirler. Bir kapanış, kapsanan kapama değişkenlerine, global kapsama kadar erişime de sahiptir.
  • Kapanışlar belleği kullanır, ancak bellek sızıntılarına neden olmaz JavaScript'in kendisi tarafından referans alınmayan kendi dairesel yapılarını temizlediği için. Kapatma gerektiren Internet Explorer bellek sızıntıları, kapakların referansını oluşturan DOM öznitelik değerlerinin bağlantısını kesemediğinde oluşturulur; bu nedenle, muhtemelen dairesel yapılara başvurular korunur.

343



Bu arada, bu "yanıtı" orijinal soruyu doğrudan ele almamak için açıklamalarla ekledim. Bunun yerine, basit bir cevabın (6 yaşındaki bir kişi için) bu karmaşık konuyla ilgili yanlış kavramlar getirmediğini umuyorum. Örneğin. Yukarıdaki popüler viki-cevap “İçsel işlevi döndürdüğünüzde bir kapanmadır” diyor. Dilbilgisel olarak yanlış olmanın dışında, bu teknik olarak yanlıştır. - dlaliberte
James, kapatmanın kapatma işlevinin çağrısı sırasında “muhtemelen” yaratıldığını söyledim, çünkü bir uygulamanın bir kapanışa kesinlikle ihtiyaç duyulduğunda karar vermesi bir süre sonra kapanmanın yaratılmasını erteleyebileceğinin akla yatkın olması. Kapama fonksiyonunda tanımlanmış iç fonksiyon yoksa, herhangi bir kapatma gerekli olmayacaktır. Bu yüzden, ilk iç fonksiyonun oluşturulmasını beklemek, böylece kapama fonksiyonunun çağrı bağlamından bir kapanma yaratmak için bekleyebilir. - dlaliberte
@ Pancar-Beetroot Kullandığımız başka bir işleve geçen bir iç fonksiyona sahip olduğumuzu varsayalım önce dış fonksiyon geri döner ve aynı iç işlevi de dış fonksiyondan döndürdüğümüzü varsayalım. Her iki durumda da aynı fonksiyon aynıdır, fakat siz dış fonksiyon geri gelmeden önce iç fonksiyonun çağrı yığına "bağlı" olduğunu söylerken, geri döndükten sonra iç fonksiyonun bir anda bir kapamaya bağlandığı söylenir. Her iki durumda da aynı şekilde davranır; Semantikler aynıdır, yani uygulama detayları hakkında konuşmuyor musunuz? - dlaliberte
@ Beetroot-Beetroot, geri bildiriminiz için teşekkürler, ve sizi düşündüğüme sevindim. Dış fonksiyonun canlı bağlamı ile fonksiyon bağlamında bir kapanış haline geldiğinde aynı bağlam arasında bir anlam farkı görmüyorum (tanımınızı anlıyorsam). İç fonksiyon umursamıyor. İçsel işlev, her iki durumda bağlam / kapanışa bir referans sağladığından ve dış işlevin çağıran kişi çağrı bağlamına referansını düştüğü için çöp toplama ilgilenmez. Ancak, insanlara kafa karıştırıcıdır ve belki de bir çağrı bağlamı olarak adlandırmak daha iyidir. - dlaliberte
Bu makalenin okunması zor ama bence söylediğim şeyi destekliyor. Şöyle diyor: "Bir işlev nesnesini [...] döndürerek veya bu tür bir işlev nesnesine, örneğin bir genel değişkene doğrudan bir referans atayarak bir kapatma oluşturulur." Ben GC'nin alakasız olduğu anlamına gelmez. Bunun yerine, GC'den dolayı ve iç işlev dış işlevin çağrı içeriğine (veya makalenin belirttiği gibi [[scope]]) eklendiğinden, dış işlev çağrısının iç ile bağlandığından dolayı döndürülemez. fonksiyon önemli olan şeydir. - dlaliberte


Tamam, 6 yaşında kapanma fanı. En basit kapanış örneğini duymak ister misiniz?

Bir sonraki durumu hayal edelim: Bir sürücü bir arabada oturuyor. O araba bir uçağın içinde. Uçak havaalanında. Sürücünün arabasının dışındaki şeylere erişebilme yeteneği, ancak uçağın içinde, bu uçak bir havaalanından ayrılsa bile, bir kapanmadır. Bu kadar. 27 yaşındayken, bak daha detaylı açıklama veya aşağıdaki örnekte.

Düzlem hikayemi kodlara nasıl dönüştürebileceğim.

var plane = function (defaultAirport) {

    var lastAirportLeft = defaultAirport;

    var car = {
        driver: {
            startAccessPlaneInfo: function () {
                setInterval(function () {
                    console.log("Last airport was " + lastAirportLeft);
                }, 2000);
            }
        }
    };
    car.driver.startAccessPlaneInfo();

    return {
        leaveTheAirport: function (airPortName) {
            lastAirportLeft = airPortName;
        }
    }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

331



İyi oynadı ve orijinal posteri cevaplar. Bence bu en iyi cevap. Bagajı da aynı şekilde kullanacaktım: büyükannenin evine gittiğini ve nintendo DS çantanı davanın içindeki oyun kartları ile paketlediğini, ancak çantayı sırt çantanıza koyduktan sonra da sırt çantanıza oyun kartları koyduğunu ve Daha sonra her şeyi bavulu cebinde daha fazla oyun kartı ile büyük bir bavulun içine koydu. Büyükannenin evine gittiğinizde, tüm dış durumlar açık olduğu sürece DS'nizde herhangi bir oyunu oynayabilirsiniz. ya da bu etki için bir şey. - slartibartfast