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


7636

İçerdiği kavramlar (örneğin işlevler, değişkenler ve benzerleri) hakkında bilgi sahibi olan ancak kapakların kendisini anlamayan birine JavaScript kapanışlarını nasıl açıklarsınız?

Wikipedia'da verilen Şema örneğini gördüm , ancak ne yazık ki yardımcı olmadı.


391
Bu ve birçok cevapla ilgili sorunum, Javascript'te kapakların neden gerekli olduğunu ve bunları kullandığınız pratik durumları açıklamaktan ziyade soyut, teorik bir bakış açısıyla yaklaşmalarıdır. Her zaman "ama neden?" Diye düşünerek gözden geçirmeniz gereken bir tl; dr makalesi ile sonuçlanırsınız. Basitçe başlıyorum: kapanışlar JavaScript'in aşağıdaki iki gerçekliği ile başa çıkmanın düzgün bir yoludur: a. kapsam blok düzeyinde değil fonksiyon seviyesindedir ve b. JavaScript'te pratikte yaptıklarınızın çoğu eşzamansız / olaya dayalıdır.
Jeremy Burton

53
@Redsandro Birincisi, olay güdümlü kod yazmayı çok daha kolay hale getirir. Sayfa yüklendiğinde HTML veya kullanılabilir özelliklerle ilgili özellikleri belirlemek için bir işlevi tetikleyebilirim. Bu işlevde bir işleyici tanımlayabilir ve ayarlayabilir ve işleyiciyi her sorgulandığında yeniden sorgulamak zorunda kalmadan tüm bu bağlam bilgilerine sahip olabilirim. Sorunu bir kez çözün, işleyicinin yeniden başlatılması üzerindeki ek yükü azaltarak bu işleyicinin gerekli olduğu her sayfada yeniden kullanın. Aynı verilerin, sahip olmadığı bir dilde iki kez yeniden eşlendiğini gördünüz mü? Kapaklar bu tür şeylerden kaçınmayı çok daha kolay hale getirir.
Erik Reppen

1
@Erik Reppen cevap için teşekkürler. Aslında, kendisini tekrar kullanan ve ek yükü aynı şekilde azaltan, ancak% 100 daha az sarma kodu gerektiren bu okunması zor closurekodun faydalarını merak ettim Object Literal.
Redsandro

6
Java programcıları için kısa cevap, bunun 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 oluşturmak).
Boris van Schooten

8
Bu pratik örneği çok kullanışlı buldum: youtube.com/watch?v=w1s9PgtEoJs
Abhi

Yanıtlar:


7358

Bir kapatma aşağıdakilerin eşleşmesidir:

  1. Bir işlev ve
  2. Bu işlevin dış kapsamına bir başvuru (sözlük ortamı)

Sözcüksel bir ortam, her yürütme bağlamının (yığın çerçevesi) bir parçasıdır ve tanımlayıcılar (yani, yerel değişken adları) ve değerler arasında bir eşlemdir.

JavaScript'teki her işlev, dış sözlük ortamına bir referans tutar. Bu başvuru, bir işlev çağrıldığında oluşturulan yürütme bağlamını yapılandırmak için kullanılır. Bu başvuru, işlevin içindeki kodun, işlevin ne zaman ve nerede çağrıldığına bakılmaksızın, işlevin dışında bildirilen değişkenleri "görmesini" sağlar.

Bir işlev, başka bir işlev tarafından çağrılan bir işlev tarafından çağrılmışsa, dış sözcüksel ortamlara bir referans zinciri oluşturulur. Bu zincire kapsam zinciri denir.

Aşağıdaki kodda, çağrıldığında inneroluşturulmuş yürütme bağlamının sözcüksel ortamıyla bir kapatma oluşturur ve değişkenin üzerine kapanır :foosecret

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

Başka bir deyişle: JavaScript'te işlevler, yalnızca kendilerinin (ve aynı sözcük ortamında bildirilen diğer işlevlerin) erişebildiği özel bir "durum kutusu" na referans taşır. Bu durum kutusu, veri gizleme ve kapsülleme için mükemmel bir mekanizma sunarak işlevin arayanı tarafından görülmez.

Ve unutmayın: JavaScript'teki işlevler değişkenler (birinci sınıf işlevler) gibi aktarılabilir, yani bu işlevsellik ve durum çiftleri programınızın etrafından geçirilebilir: C ++ 'da bir sınıf örneğini nasıl geçirebileceğinize benzer.

JavaScript kapanışları yoktu, o daha devlet fonksiyonları arasında iletilmesi gerekir açıkça parametre listeleri uzun ve kod gürültülü hale.

Bu nedenle, bir işlevin her zaman özel bir devlet parçasına erişmesini istiyorsanız, bir kapatma kullanabilirsiniz.

... ve sık sık biz do bir işlevle ilişkilendirmek devlet istiyoruz. Örneğin, Java veya C ++ 'da, bir sınıfa özel örnek değişkeni ve yöntem eklediğinizde, durumu işlevsellikle ilişkilendirirsiniz.

C ve diğer birçok yaygın dilde, bir işlev döndükten sonra, yığın karesi yok edildiğinden tüm yerel değişkenlere artık erişilemez. JavaScript'te, bir işlevi başka bir işlev içinde bildirirseniz, dış işlevin yerel değişkenlerine işlev döndükten sonra erişilebilir kalabilir. Bu sayede gerçi yukarıda üzerinde kodu, secretfonksiyon nesnesi için kullanılabilir kalır inner, daha sonra bu döndü edilmiştir foo.

Kapanışların Kullanımları

Bir işlevle ilişkilendirilmiş özel duruma ihtiyacınız olduğunda, kapanışlar yararlıdır. Bu çok yaygın bir senaryodur ve unutmayın: JavaScript'in 2015 yılına kadar sınıf sözdizimi yoktu ve yine de özel alan sözdizimi yok. Kapaklar bu ihtiyacı karşılar.

Özel Bulut Değişkenleri

Aşağıdaki kodda, fonksiyon toStringaracın ayrıntılarını kapatır.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Fonksiyonel Programlama

Aşağıdaki kodda, işlev innerhem fnve 'i kapatır args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Olay Odaklı Programlama

Aşağıdaki kodda, işlev onClickdeğişkeni kapatır BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

modularization

Aşağıdaki örnekte, tüm uygulama ayrıntıları hemen yürütülen bir işlev ifadesinin içine gizlenmiştir. Özel durum üzerindeki işlevler tickve toStringkapanış ve çalışmalarını tamamlamak için ihtiyaç duydukları işlevler. Kapanışlar, kodumuzu modüler hale getirmemizi ve kapsüllememizi sağladı.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Örnekler

örnek 1

Bu örnek, yerel değişkenlerin kapakta kopyalanmadığını gösterir: Kapak, orijinal değişkenlerin kendilerine bir referans tutar . Dış işlev çıktıktan sonra bile yığın çerçevesi bellekte canlı kalır.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

ÖRNEK 2

Aşağıdaki kodu, üç yöntemde de log, incrementve updateaynı sözlü ortamla her yerinde yakın.

Ve her createObjectçağrıldığında, yeni bir yürütme bağlamı (yığın çerçevesi) oluşturulur ve tamamen yeni bir değişken xve logbu yeni değişkenin üzerinde kapanan yeni bir işlevler ( vb.) Oluşturulur.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

ÖRNEK 3

Kullanarak bildirilen değişkenleri kullanıyorsanız var, hangi değişkeni kapattığınızı anladığınızdan emin olun. Kullanılarak bildirilen değişkenler varkaldırılır. Bunun nedeni getirilmesi çok kısa bir mesafede modern JavaScript bir sorun olduğunu letve const.

Aşağıdaki kodda, döngü etrafında her seferinde inner, kapanan yeni bir işlev oluşturulur i. Ancak var idöngünün dışında kaldığı için, bu iç fonksiyonların tümü aynı değişkenin üzerine kapanır, yani i(3) ' ün son değeri üç kez yazdırılır.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Son puanlar:

  • JavaScript'te bir işlev bildirildiğinde bir kapatma oluşturulur.
  • Bir dönen functiondış işlev içinde durum dış fonksiyonlu tamamlandıktan sonra bile, geri iç işlevine dolaylı kullanılabilir çünkü başka bir işlev içinden, bir kapağın klasik bir örneğidir.
  • eval()Bir fonksiyonun içinde her kullandığınızda , bir kapatma kullanılır. evalFonksiyonun yerel değişkenlerine başvurabileceğiniz metin ve katı olmayan modda kullanarak yeni yerel değişkenler bile oluşturabilirsiniz eval('var foo = …').
  • Bir işlev içinde new Function(…)( İşlev yapıcısı ) kullandığınızda , sözcüksel ortamı üzerinde kapanmaz: bunun yerine genel bağlam üzerinde kapanır. Yeni işlev, dış işlevin yerel değişkenlerine başvuramaz.
  • JavaScript'teki bir kapatma , işlev beyanı noktasında kapsama bir referans ( bir kopya DEĞİL ) tutmaya benzer , bu da dış kapsamına bir referans tutar ve böylece, üstündeki global nesneye kadar devam eder. kapsam zinciri.
  • Bir işlev bildirildiğinde bir kapatma oluşturulur; bu kapatma işlevi işlev çağrıldığında yürütme bağlamını yapılandırmak için kullanılır.
  • Bir işlev her çağrıldığında yeni bir yerel değişkenler kümesi oluşturulur.

Bağlantılar


74
Bu kulağa hoş geliyor: "JavaScript'teki bir kapanış, bir fonksiyondan çıkıldığında olduğu gibi tüm yerel değişkenlerin bir kopyasını tutmak gibidir." Ancak birkaç nedenden dolayı yanıltıcıdır. (1) Bir kapanış oluşturmak için işlev çağrısının çıkması gerekmez. (2) Yerel değişkenlerin değerlerinin değil, değişkenlerin kendilerinin bir kopyasıdır . (3) Bu değişkenlere kimin erişimi olduğunu söylemez.
dlaliberte

27
Örnek 5, kodun istendiği gibi çalışmadığı bir "gotcha" yı gösterir. Ama nasıl düzeltileceğini göstermez. Bu diğer cevap bunu yapmanın bir yolunu gösteriyor.
Matt

190
Bu yazının "Kapanışlar Sihirli Değil" diyen büyük kalın harflerle nasıl başladığını ve ilk örneğini "Sihir, JavaScript'te bir işlev başvurusunun oluşturulduğu kapanışa gizli bir referansı olduğu" şeklinde bitirmesini seviyorum.
Andrew Macheret

6
Örnek 3, kapakların kaldırma javascripts ile karıştırılmasıdır. Şimdi kaldırma mekanizmasını getirmeden sadece kapakların açıklanmasının yeterince zor olduğunu düşünüyorum. : Bu bana en çok yardımcı Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.gelen developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Caramba

3
ECMAScript 6, bu harika makalede kapanma ile ilgili bir şeyi değiştirebilir. Örneğin, Örnek 5'te kullanmak let i = 0yerine, orijinal olarak istediğinizi yazdıracaktır. var i = 0testList()
Nier

3988

JavaScript'teki her işlev, dış sözlük ortamına bir bağlantı sağlar. Sözcüksel ortam, bir kapsamdaki tüm adların (örn. Değişkenler, parametreler) değerleri ile birlikte bir haritasıdır.

Bu nedenle, functionanahtar kelimeyi her gördüğünüzde, bu işlevin içindeki kod, işlevin dışında bildirilen değişkenlere erişime sahiptir.

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

Bu günlüğe 16fonksiyon için barbir parametre üzerinde kapanır xve değişken tmpdış fonksiyonunun sözcük ortamda mevcut her ikisi de, foo.

İşlev bar, sözcüksel işlev ortamıyla bağlantısı ile birlikte foobir kapanıştır.

Kapatma oluşturmak için bir işlevin geri dönmesi gerekmez . Sadece beyanı sayesinde, her fonksiyon çevreleyen sözcüksel ortamı kapatır ve bir kapanış 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(10); // 16
bar(10); // 17

Yukarıdaki işlev de 16 günlüğünü kaydeder, çünkü içerideki kod artık doğrudan kapsamda olmasalar bile barargüman xve değişkene başvurabilir tmp.

Bununla birlikte, tmphala barkapanış içinde asılı olduğundan , artırılması mümkündür. Her aradığınızda artacaktır bar.

Bir kapanışı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 ecoluşturulur. İşlev bağımsız değişkenleri ve hedef nesneyle birlikte, bu yürütme bağlamı, çağıran yürütme bağlamının sözcüksel ortamına da bir bağlantı alır, yani dış sözcüksel ortamda (yukarıdaki örnekte hem ave hem de b) bildirilen değişkenler kullanılabilir ec.

Her fonksiyon bir kapanış yaratır, çünkü her fonksiyonun dış sözcüksel ortamına bir bağlantısı vardır.

Değişkenlerin kendilerinin kopyadan değil , bir kapaktan görülebildiğini unutmayın .


24
@feeela: Evet, her JS işlevi bir kapatma oluşturur. Başvurulmayan değişkenler muhtemelen modern JS motorlarında çöp toplama için uygun hale getirilecektir, ancak bir yürütme bağlamı oluşturduğunuzda, bu bağlamın ek yürütme bağlamına ve değişkenlerine bir referansı olduğu gerçeğini değiştirmez ve bu işlev, orijinal referansı korurken, farklı bir değişken kapsamına taşınması potansiyeli olan bir nesnedir. Kapanış bu.

@Ali Az önce sağladığım jsFiddle'ın aslında hiçbir şey kanıtlamadığını keşfettim delete. Bununla birlikte, işlevi tanımlayan ifade yürütüldüğünde, işlevin [[Kapsam]] olarak taşıyacağı (ve sonuçta çağrıldığında kendi sözcüksel ortamı için bir temel olarak kullanacağı) sözcüksel ortam belirlenir. Fonksiyon bu Bu aracı olup bağımsız olarak aslında ve bu kapsamı kaçar olup ifade eder değerleri, yürütme kapsamının tüm içeriği üzerinde kapatma. Lütfen spesifikasyonlardaki
Asad Saeeduddin

8
İlkel türleri ve referansları açıklamaya çalışana kadar bu iyi bir cevaptı. Bunu tamamen yanlış anlıyor ve kopyalanan gerçeklerden bahsediyor, ki bu gerçekten hiçbir şeyle ilgisi yok.
Ry-

12
Kapanışlar JavaScript'in sınıf tabanlı, nesne yönelimli programlamaya verdiği yanıttır. JS sınıf temelli değildir, bu yüzden başka türlü uygulanamayan bazı şeyleri uygulamak için başka bir yol bulmak zorundaydı.
Bartłomiej Zalewski

2
kabul edilen cevap bu olmalıdır. Sihir asla iç işlevde olmaz. Dış fonksiyon bir değişkene atandığında olur. Bu, iç işlev için yeni bir yürütme bağlamı oluşturur, böylece "özel değişken" biriktirilebilir. Tabii ki değişken dış fonksiyon atanmış beri bağlam korumak olabilir. İlk cevap, orada gerçekten ne olduğunu açıklamadan her şeyi daha karmaşık hale getirir.
Albert Gao

2442

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

Yaşlı Albert'in dediği gibi: "Eğer altı yaşında bir çocuğa açıklayamazsan, bunu gerçekten kendin anlamıyorsun."

Herkes benim 6 olduğumu ve garip bir şekilde bu konuyla ilgilendiğimi düşünebilir mi?

İlk soruyu tam anlamıyla almaya çalışan tek kişiden biri olduğuma eminim. O zamandan beri, soru birkaç kez mutasyona uğradı, bu yüzden cevabım şimdi inanılmaz saçma ve yersiz görünebilir. Umarım hikayenin genel fikri bazıları için eğlenceli olmaya devam eder.


Zor kavramları açıklarken büyük bir analoji ve metafor hayranıyım, bu yüzden elimi bir hikaye ile deneyeyim.

Bir Zamanlar:

Bir prenses vardı ...

function princess() {

Maceralarla dolu harika bir dünyada yaşıyordu. Prens Charming'le tanıştı, tek boynuzlu at, savaşan ejderhalar, konuşan hayvanlarla ve diğer birçok fantastik şeyle dünyasında dolaştı.

    var adventures = [];

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

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

    /* ... */

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

    return {

Ve sık sık onlara en son şaşırtıcı macerasını bir prenses olarak söylerdi.

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

Ama bütün gördükleri küçük bir kız ...

var littleGirl = princess();

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

littleGirl.story();

Ve yetişkinler gerçek prensesleri bilseler bile, asla tek boynuzlu atlara veya ejderhalara inanmazlar çünkü onları asla göremezlerdi. Yetişkinler, sadece küçük kızın hayal gücünde var olduklarını söyledi.

Ama gerçek gerçeği biliyoruz; içinde prenses olan küçük kız ...

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


340
Bu açıklamayı gerçekten seviyorum. Bunu okuyan ve takip etmeyenler için, benzetme şöyledir: princess () işlevi, özel verileri içeren karmaşık bir kapsamdır. Fonksiyonun dışında, özel veriler görülemez veya erişilemez. Prenses, tek boynuzlu atları, ejderhaları, maceraları vb. Hayalinde (özel veriler) tutar ve yetişkinler onları kendileri göremez. AMA Prensesin hayal gücü, örneğin sihir dünyasına maruz bıraktığı story()tek arayüz olan fonksiyonun kapanmasında yakalanır littleGirl.
Patrick M

Yani burada storykapatma ama been kodu vardı var story = function() {}; return story;sonra littleGirlkapatma olacaktır. En azından MDN'in 'özel' yöntemlerini kapanışlarla kullanmasından edindiğim izlenim : "Bu üç kamu işlevi aynı ortamı paylaşan kapaklardır."
icc97

16
@ icc97, evet, storykapsamında sağlanan çevreye gönderme yapan bir kapaktır princess. princessaynı zamanda başka bir zımni kapanmadır, yani princessve var olan ve tanımlanan ortam / kapsamda var littleGirlolan bir parentsdiziye yapılan herhangi bir başvuruyu paylaşır . littleGirlprincess
Jacob Swartwood

6
@BenjaminKrupp Vücutta yazılanlardan daha fazla işlem olduğunu göstermek / ima etmek için açık bir kod yorumu ekledim princess. Ne yazık ki bu hikaye şu anda bu konu üzerinde biraz yer kaplıyor. Başlangıçta soru "5 yıl eski bir JavaScript kapanışlarını açıklamak" soruyordu; benim cevabım bunu yapmaya çalışan tek kişiydi. Berbat bir şekilde başarısız olacağından şüphe etmiyorum, ama en azından bu yanıtın 5 yaşında bir çocuğun ilgisini çekme şansı olabilirdi.
Jacob Swartwood

11
Aslında, bu bana çok mantıklı geldi. Ve itiraf etmeliyim, nihayet prenseslerin ve maceraların masallarını kullanarak bir JS kapanışını anlamak beni biraz garip hissettiriyor.
kristalleş

753

Soruyu ciddiye alırsak, tipik bir 6 yaşındaki bir çocuğun bilişsel olarak ne yapabileceğini öğrenmeliyiz, ancak itiraf etmek gerekirse, JavaScript ile ilgilenen biri çok tipik değildir.

Çocukluk Gelişimi Üzerine : 5-7 Yıl diyor:

Çocuğunuz iki adımlı talimatları takip edebilecektir. Örneğin, çocuğunuza "Mutfağa gidin ve bana çöp torbası alın" derseniz, bu yönü hatırlayabileceklerdir.

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

Mutfak, yerel değişken adı verilen bir kapaktır trashBags. Mutfakta getTrashBagbir çöp torbası alıp geri döndüren bir işlev var .

Bunu JavaScript'te şu şekilde kodlayabiliriz:

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

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

var kitchen = makeKitchen();

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

Kapakların neden ilginç olduğunu açıklayan diğer noktalar:

  • Her makeKitchen()çağrıldığında, kendi ayrı ile yeni bir kapanış oluşturulur trashBags.
  • trashBagsDeğişken Her mutfakta içine yerel ve erişilebilir dışında değil, ama üzerinde iç fonksiyon getTrashBagözelliği bu bilgilere erişimi var.
  • Her işlev çağrısı bir kapak oluşturur, ancak kapağın iç kısmına erişimi olan bir iç işlev, kapağın dışından çağrılmadıkça, kapağın çevresinde kalmasına gerek yoktur. Nesneyi getTrashBagişlevle döndürmek burada bunu yapar.

6
Aslında, kafa karıştırıcı bir şekilde, makeKitchen işlev çağrısı , döndürdüğü mutfak nesnesi değil, gerçek kapanıştır.
dlaliberte

6
Diğerleri arasından geçerken, bu cevabı kapanışların ne ve nedenini açıklamanın en kolay yolu olarak buldum.
Chetabahana

3
Çok fazla menü ve meze, yeterli et ve patates. Bu cevabı sadece bir kısa cümle ile geliştirebilirsiniz: "Kapatma, bir fonksiyonun kapalı bağlamıdır, çünkü sınıflar tarafından sağlanan kapsam belirleme mekanizmasının olmaması."
Staplerfahrer

584

Saman Adamı

Bir düğmenin kaç kez tıklandığını ve her üç tıklamayla bir şey yapmam gerektiğini bilmeliyim ...

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şken ekleyerek dış kapsama giriyor. Bazı durumlarda, dış uygulamanızın bu bilgilere erişmesi gerekebileceğinden bu tercih edilebilir. Ancak bu durumda, yalnızca her üçüncü tıklamanın davranışını değiştiriyoruz, bu nedenle bu işlevselliği olay işleyicisine dahil etmek tercih edilir .

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ç şeye dikkat edin.

Yukarıdaki örnekte, JavaScript'in kapatma davranışını kullanıyorum. Bu davranış, herhangi bir işlevin süresiz olarak oluşturulduğu kapsama erişmesine izin verir. Bunu pratik olarak uygulamak için hemen başka bir işlev döndüren bir işlevi çağırırım ve döndürdüğüm işlevin iç sayım değişkenine erişimi olduğu için (yukarıda açıklanan kapatma davranışı nedeniyle) bu, sonuçta ortaya çıkan kullanım için özel bir kapsamla sonuçlanır. işlevi ... Çok basit değil mi? Hadi sulandıralım ...

Basit 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 döndürülen işlev nesnesi için doğrudan kullanılamaz ...

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

Anla? Bu nedenle birincil örneğimizde, count değişkeni kapatma içinde bulunur ve olay işleyicisi tarafından her zaman kullanılabilir, böylece durumunu tıklatmadan tıklatmaya devam eder.

Ayrıca, bu özel değişken durumuna hem okumalar hem de özel kapsamlı değişkenlerine atama için tamamen erişilebilir.

İşte böyle; Artık bu davranışı tamamen kapsüllemiş oluyorsunuz.

Tam Blog Yayını (jQuery değerlendirmeleri de dahil)


11
Kapanışın ne olduğuyla ilgili tanımınıza katılmıyorum. Kendi kendini çağırması için bir neden yok. Aynı zamanda bu biraz basit (ve yanlış) o (Bu sorunun üst cevap yorumlarında bu tartışmanın çok) "iade" edilmesi gerekir demek
James Montagne

40
@ Desagree bile, onun örneği (ve tüm yazı) gördüğüm en iyi biridir. Soru eski olmasa da benim için çözülmüş olsa da, + 1'i hak ediyor.
e-satis

84
"Bir düğmenin kaç kez tıklandığını bilmem ve her üç tıklamayla bir şeyler yapmam gerekiyor ..." BU dikkatimi çekti. Bir kullanım durumu ve bir kapamanın nasıl gizemli bir şey olmadığını ve birçoğumuz bunları yazıyor, ancak resmi adı tam olarak bilmediğimizi gösteren çözüm.
Chris22

Güzel bir örnek, çünkü 2. örnekteki "sayım" ın "sayım" değerini koruduğunu ve "eleman" her tıklandığında 0'a sıfırlamadığını gösterir. Çok bilgilendirici!
Adam

Kapatma davranışı için +1 . Biz sınırlamak Can kapatma davranışı için fonksiyonlar javascript veya bu kavram da dilin diğer yapılara uygulanabilir?
Dziamid

492

Kapanışların açıklanması zordur çünkü herkesin sezgisel bir şekilde çalışmayı beklediği bazı davranışlar için kullanılırlar. Ben (o ve yol onları açıklamak için en iyi yolu bulmak ben onlarsız durumu hayal etmek ne yaptıklarını öğrendik) olduğu:

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

JavaScript eğer burada ne olur değil mi kapanışlarını biliyor? Son satırdaki çağrıyı yöntem gövdesi ile değiştirin (temel olarak işlev çağrılarının yaptığıdır) ve şunları elde edersiniz:

console.log(x + 3);

Şimdi, tanımı xnedir? Mevcut kapsamda tanımlayamadık. Tek çözüm, kapsamını (veya daha doğrusu ebeveyninin kapsamını) plus5 taşımasına izin vermektir . Bu şekilde, xiyi tanımlanmış ve 5 değerine bağlıdır.


11
Bu tam olarak birçok kişiyi , değişken değişkenin kendisi değil, döndürülen işlevde kullanılan değerler olduğunu düşünmeye yönlendiren bir örnektir . Eğer "return x + = y" ya da daha iyisi hem bu hem de başka bir işlev "x * = y" olarak değiştirilmişse, hiçbir şeyin kopyalanmadığı açıktır. Çerçeveleri biriktiren insanlar için, bunun yerine işlev döndükten sonra da var olabilecek yığın çerçevelerini kullandığınızı hayal edin.
Matt

14
@Matte ben katılmıyorum. Bir örnek, tüm özellikleri ayrıntılı bir şekilde belgelemek zorunda değildir . Olması gerekiyordu indirgeyici ve bir kavramın belirgin özelliğini göstermektedir. OP basit bir açıklama istedi (“altı yaşında bir çocuk için”). Kabul edilen cevabı alın: Tam olarak kapsamlı olmaya çalıştığı için kısa bir açıklama yapmada tamamen başarısız olur . (Bağlamanın değerden ziyade referans olarak olması önemli bir JavaScript özelliği olarak kabul ediyorum ... ancak yine de başarılı bir açıklama çıplak asgari seviyeye indiren bir açıklamadır.)
Konrad Rudolph

@KonradRudolph Örneğinizin tarzını ve kısaluğunu seviyorum. Basitçe biraz değiştirmenizi tavsiye ederim, böylece "Tek çözüm ..." son kısmı gerçekleşir. Şu anda aslında yok senaryonuz, başka, daha basit bir çözümdür değil javascript continuations karşılık gelir ve yapar devamları olan hakkında bir Sanıldığının karşılık gelmektedir. Dolayısıyla mevcut haliyle örnek tehlikelidir. Bunun özellikleri kapsamlı bir şekilde listelemesiyle ilgili değildir, x'in döndürülen işlevde ne olduğunu anlamakla ilgilidir, sonuçta ana nokta.
Matt

@Matt Hmm, seni tam olarak anladığımdan emin değilim ama geçerli bir noktaya sahip olabileceğini görmeye başladım. Yorumlar çok kısa olduğu için, bir gist / pastie veya bir sohbet odasında ne demek istediğinizi açıklayabilir misiniz? Teşekkürler.
Konrad Rudolph

2
@KonradRudolph Bence x + = y'nin amacı hakkında net değildim. Amaç, yalnızca döndürülen işleve tekrarlanan çağrıların aynı değişkeni x kullanmaya devam ettiğini göstermekti ( işlev yaratıldığında insanların "eklendiğini" düşündüğü aynı değerin aksine ). Bu, kemandaki ilk iki uyarı gibidir. X * = y işlevinin amacı, döndürülen birden çok işlevin aynı x değerini paylaştığını göstermektir.
Matt

376

Tamam, 6 yaşındaki kapama hayranı. En basit kapatma örneğini duymak ister misiniz?

Bir sonraki durumu hayal edelim: bir sürücü arabada oturuyor. Bu 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 o uçak bir havaalanından ayrılsa bile, uçağın içinde bir kapanış. Bu kadar. 27 yaşına geldiğinizde, daha ayrıntılı açıklamaya veya aşağıdaki örneğe bakın.

Uçak hikayemi koda 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");


26
İyi oynanan ve orijinal posteri cevaplar. Bence bu en iyi cevap. Ben de benzer şekilde bagaj kullanacaktım: büyükannenin evine gittiğinizi ve nintendo DS kasanızı kasanızın içinde oyun kartları ile paketlediğinizi, ancak daha sonra kasayı sırt çantanızın içine koyduğunuzu ve ayrıca oyun kartlarını sırt çantanızın cebine koyduğunuzu ve O zaman her şeyi büyük bir bavulun içine bavulun cebinde daha fazla oyun kartı koydunuz. Büyükannenin evine gittiğinizde, tüm dış durumlar açık olduğu sürece DS'nizde herhangi bir oyun oynayabilirsiniz. ya da bu yönde bir şey.
slartibartfast

376

TLDR

Kapatma, bir işlev ve dış sözcüksel (yani yazılı olarak) ortamı arasındaki bir bağlantıdır, öyle ki o ortamda tanımlanan tanımlayıcılar (değişkenler, parametreler, işlev bildirimleri vb.), Ne zaman veya ne olursa olsun, işlevin içinden görülebilir burada işlev çağrılır.

ayrıntılar

ECMAScript spesifikasyonunun terminolojisinde, bir fonksiyonun tanımlandığı sözlüksel ortama[[Environment]] işaret eden her fonksiyon nesnesinin referansı ile bir kapatma gerçekleştirildiği söylenebilir .

Bir fonksiyon, iç üzerinden çağrıldığında [[Call]]yöntem, [[Environment]]fonksiyon bir nesne üzerinde bir referans kopyalanır dış çevre referans bölgesinin çevre kayıt yeni oluşturulan bir yürütme içeriği (yığın çerçeve).

Aşağıdaki örnekte işlev f, global yürütme bağlamının sözcüksel ortamı üzerinde kapanır:

function f() {}

Aşağıdaki örnekte işlev, işlevsel hsözcüksel ortamını gkapatır ve bu da küresel yürütme bağlamının sözcüksel ortamını kapatır.

function g() {
    function h() {}
}

Bir iç işlev bir dış tarafından döndürülürse, dış işlev döndükten sonra dış sözcüksel ortam devam eder. Bunun nedeni, iç işlev sonunda çağrıldığında dış sözcüksel ortamın kullanılabilir olması gerektiğidir.

Aşağıdaki örnekte işlev, işlevin jsözcüksel ortamı üzerinde kapanır i; bu , işlev yürütmeyi tamamladıktan çok sonra değişkenin xiç işlevden görülebileceği anlamına gelir :ji

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

Bir kapatma, dış sözcük ortamda değişkenler kendilerini mevcuttur, vardır değil kopyaları.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

Dış çevre referansları aracılığıyla yürütme bağlamları arasında bağlanan sözcüksel ortamlar zinciri, bir kapsam zinciri oluşturur ve herhangi bir işlevden görülebilen tanımlayıcıları tanımlar.

Netliği ve doğruluğu artırmak için bu cevabın orijinalinden önemli ölçüde değiştiğini lütfen unutmayın.


56
Vay canına, dize yerine koymalarını bu şekilde kullanabileceğinizi asla bilmiyordum console.log. Başka biri ilgileniyorsa daha fazlası var: developer.mozilla.org/en-US/docs/DOM/…
Flash

7
Fonksiyonun parametre listesindeki değişkenler de kapanışın bir parçasıdır (örn var.
Thomas Eding

Kapanışlar daha çok nesneler ve sınıflar gibi geliyor. Pek çok insan neden bu ikisini karşılaştırmıyor - yeni başlayanların öğrenmesi daha kolay olurdu!
almaruf

365

Bu, diğer cevapların bazılarında görünen kapaklarla ilgili (olası) yanlış anlamaları giderme girişimidir.

  • Kapatma yalnızca bir iç işlevi döndürdüğünüzde oluşturulmaz. Aslında, kapatma işlevinin kapanabilmesi için kapatma işlevinin geri dönmesine gerek yoktur . Bunun yerine, iç fonksiyonunuzu bir dış kapsamdaki bir değişkene atayabilir veya hemen ya da daha sonra çağrılabileceği başka bir fonksiyona argüman olarak iletebilirsiniz. Bu nedenle, herhangi bir iç işlev, iç işlev her çağrıldığında, kapalı işlev geri döndükten önce veya sonra bu kapatmaya erişebileceğinden , kapalı işlevinin kapatılması büyük olasılıkla kapalı işlev çağrıldığında oluşturulur .
  • Kapatma , kapsamındaki değişkenlerin eski değerlerinin bir kopyasına başvurmaz. Değişkenlerin kendileri kapanışın bir parçasıdır ve bu nedenle bu değişkenlerden birine erişirken görülen değer, erişildiği andaki en son değerdir. Bu nedenle, döngülerin içinde oluşturulan iç işlevler zor olabilir, çünkü her biri işlevin oluşturulduğu veya çağrıldığı sırada değişkenlerin bir kopyasını almak yerine aynı dış değişkenlere erişebilir.
  • Bir kapanıştaki "değişkenler", işlev içinde bildirilen adlandırılmış işlevleri içerir . Ayrıca işlevin argümanlarını da içerirler. Bir kapak, küresel kapsayıcıya kadar içerdiği kapak değişkenlerine de erişebilir.
  • Kapaklar bellek kullanır, ancak JavaScript kendiliğinden referans alınmayan kendi dairesel yapılarını temizlediğinden bellek sızıntılarına neden olmazlar. Kapakları içeren Internet Explorer bellek sızıntıları, kapanışlara başvuruda bulunan DOM özniteliği değerlerinin bağlantısını kesemediğinde oluşturulur ve böylece büyük olasılıkla dairesel yapılara yapılan başvurular korunur.

15
James, kapatmanın "muhtemelen" kapalı fonksiyonun çağrılması sırasında yaratıldığını söyledim, çünkü bir uygulamanın bir kapanışın yaratılmasını ertelemenin bir zamana kadar kapatılmasının kesinlikle gerekli olduğuna karar vermesi mantıklıdır. Eğer kapatma fonksiyonunda tanımlı bir dahili fonksiyon yoksa, herhangi bir kapatma gerekmeyecektir. Bu yüzden belki de ilk iç fonksiyon yaratılana kadar bekleyebilir, daha sonra çevreleme fonksiyonunun çağrı bağlamından bir kapanış yaratabilir.
dlaliberte

9
@ Pancar-Pancar Dış fonksiyon dönmeden önce kullanıldığı başka bir fonksiyona geçirilen bir iç fonksiyonumuz olduğunu varsayalım ve aynı iç fonksiyonu dış fonksiyondan da geri döndürdüğümüzü varsayalım. Her iki durumda da aynı işlevdir, ancak dış işlev dönmeden önce iç işlevin çağrı yığınına "bağlı" olduğunu, geri döndükten sonra iç işlevin aniden bir kapanmaya bağlı olduğunu söylüyorsunuz. Her iki durumda da aynı şekilde davranır; anlambilim aynıdır, bu yüzden sadece uygulama ayrıntılarından bahsetmiyor musunuz?
dlaliberte

7
@ Beetroot-Beetroot, geri bildiriminiz için teşekkürler ve sizi düşündüğüme sevindim. Dış fonksiyonun canlı bağlamı ve aynı bağlam, fonksiyon geri döndüğünde kapanma olduğunda (tanımınızı anlarsam) arasında herhangi bir anlamsal fark görmüyorum. İç fonksiyon umursamaz. Çöp toplama umursamaz, çünkü iç fonksiyon içerik / kapanışa her iki şekilde de bir referans sağlar ve dış fonksiyonun arayanı sadece çağrı içeriğine olan referansını bırakır. Ancak insanlar için kafa karıştırıcıdır ve belki de sadece çağrı bağlamı olarak adlandırmak daha iyidir.
dlaliberte

9
Bu makaleyi okumak zor, ama aslında söylediklerimi desteklediğini düşünüyorum. Diyor ki: "Bir fonksiyon nesnesi [...] döndürülerek veya böyle bir fonksiyon nesnesine örneğin bir global değişkene doğrudan bir referans atanarak bir kapatma oluşur." GC'nin alakasız olduğunu kastetmiyorum. Bunun yerine, GC nedeniyle ve iç işlev dış işlevin çağrı içeriğine (veya makalenin söylediği gibi [[kapsam]]) bağlı olduğundan, dış işlev çağrısının geri dönüp dönmediği önemli değildir, çünkü iç önemli olan işlevdir.
dlaliberte

3
Mükemmel cevap! Eklemeniz gereken bir şey, tüm işlevlerin tanımlandıkları yürütme kapsamının tüm içeriğini kapatmasıdır. Üst kapsamdaki değişkenlerden bazılarına veya hiç birine değinmeleri önemli değildir: üst kapsamın sözcüksel ortamına başvuru koşulsuz olarak [[Kapsam]] olarak depolanır. Bu, ECMA spesifikasyonundaki fonksiyon yaratma bölümünden görülebilir.
Asad Saeeduddin

236

Bir süre önce kapanışları açıklayan bir blog yazısı yazdım. Kapanışlar hakkında neden bir tane isteyeyim diye söylediklerim burada.

Kapanışlar, bir işlevin kalıcı, özel değişkenlere sahip olmasına izin vermenin bir yoludur - yani, yalnızca bir işlevin bildiği, çalıştığı önceki zamanlardan gelen bilgileri izleyebildiği değişkenlerdir.

Bu anlamda, bir işlevin biraz özel niteliklere sahip bir nesne gibi davranmasına izin verirler.

Tüm yayın:

Peki bu kapatma olayı nedir?


Öyleyse, kapanışların ana yararı bu örnekle vurgulanabilir mi? Diyelim ki bir işlev emailError (sendToAddress, errorString) Diyelim devError = emailError("devinrhode2@googmail.com", errorString)ve sonra paylaşılan bir emailError işlevinin kendi özel sürümüm var mı?
Devin G Rhode

Bu açıklama ve (kapatma thingys) bağlantısındaki ilişkili mükemmel örnek, kapanışları anlamanın en iyi yoludur ve en üstte olmalıdır!
HopeKing

215

Kapaklar basit:

Aşağıdaki basit örnek, JavaScript kapatmalarının tüm ana noktalarını kapsamaktadır. *  

İşte ekleyebilen ve çarpabilen hesap makineleri üreten bir fabrika:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Anahtar nokta: Her çağrı, make_calculatoryeni bir yerel değişken oluşturur n, bu da o hesap makinesi tarafından kullanılmaya devam eder addve multiplyiadelerden çok sonra çalışır make_calculator.

Yığın karelerine aşina iseniz, bu hesap makineleri garip görünüyor: İadelerden nsonra nasıl erişmeye devam edebilirler make_calculator? Yanıt, JavaScript'in "yığın kareleri" kullanmadığını, bunun yerine geri dönen işlev çağrısından sonra da devam edebilen "yığın kareleri" kullandığını hayal etmektir.

Bir dış işlevde ** bildirilen değişkenlere erişen addve gibi iç işlevlere kapatma adı verilir .multiply

Kapaklar için hemen hemen hepsi bu.



* Örneğin, başka bir yanıtta verilen "Aptallar için Kapanışlar" makalesindeki tüm noktaları kapsar , örnek 6 hariç, değişkenlerin bildirilmeden önce kullanılabileceğini gösterir, bilmek güzel ama gerçekte kapanışlarla ilgisizdir. Ayrıca , işlevlerini bağımsız değişkenlerini yerel değişkenlere (adlandırılan işlev bağımsız değişkenleri) kopyalayan (1) ve (2) sayıları kopyalamanın yeni bir sayı oluşturduğu ancak bir nesne başvurusunu kopyaladığı noktalar (1) dışında , kabul edilen yanıttaki tüm noktaları kapsar. size aynı nesneye başka bir başvuru verir. Bunları bilmek de iyidir, ancak kapanışlarla tamamen ilgisizdir. Aynı zamanda, örneğin çok benzer bu cevap ama daha kısa ve daha az soyut ısırdı. Noktasını kapsamazbu cevap veya bu yorum , yani JavaScript, akımınDöngü değişkeninin iç işlevinize değeri: "Takma" adımı yalnızca iç işlevinizi çevreleyen ve her döngü yinelemesinde çağrılan bir yardımcı işlevle yapılabilir. (Kesin olarak söylemek gerekirse, iç işlev, takılı bir şeyin olması yerine yardımcı işlevin değişkeninin kopyasına erişir.) Yine, kapaklar oluştururken çok yararlıdır, ancak bir kapağın ne olduğu veya nasıl çalıştığının bir parçası değildir. Değişkenlerin depolama alanından ziyade değerlere bağlı olduğu ML gibi işlevsel dillerde farklı şekilde çalışan kapaklar nedeniyle ek karışıklık vardır, bu da kapanmaları bir şekilde (yani "takma" yolu) anlayan insanların sürekli bir akışını sağlar. değişkenlerin her zaman depolama alanına bağlı olduğu ve asla değerlere bağlı olmadığı JavaScript için yanlıştır.

** Herhangi bir dış fonksiyon, eğer birden fazla iç içe ise, hatta küresel bağlamda, bu cevap açıkça işaret ettiği için.


Çağırırsanız ne olur: second_calculator = first_calculator (); second_calculator = make_calculator () yerine; ? Aynı olmalı, değil mi?
Ronen Festinger

4
@Ronen: first_calculatorBir nesne (işlev değil) second_calculator = first_calculator;olduğundan, işlev çağrısı değil, bir atama olduğu için parantez kullanmamalısınız . Sorunuzu cevaplamak için, make_calculator'a yalnızca bir çağrı olurdu, bu yüzden sadece bir hesap makinesi yapılacaktı ve first_calculator ve second_calculator değişkenlerinin ikisi de aynı hesap makinesine atıfta bulunacaktı, bu yüzden cevaplar 3, 403, 4433, 44330 olurdu.
Matt

204

Bunu altı yaşında bir çocuğa nasıl açıklarım:

Yetişkinlerin bir eve nasıl sahip olabileceğini biliyor musunuz ve eve mi diyorlar? Bir annenin bir çocuğu olduğunda, çocuğun gerçekten hiçbir şeyi yoktur, değil mi? Ancak ebeveynlerinin bir evi vardır, bu yüzden birisi çocuğa "Eviniz nerede?" Diye sorduğunda, "o evi!" Diye cevaplayabilir ve ebeveynlerinin evini gösterebilir. “Kapanış” çocuğun her zaman (yurtdışında da olsa) bir evi olduğunu söyleyebilme yeteneğidir, ancak gerçekten ebeveynin evi olmasına rağmen.


200

5 yaşındaki bir çocuğa kapanışları açıklayabilir misiniz? *

Hala Google'ın açıklaması çok iyi ve özlü olduğunu düşünüyorum :

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

İç fonksiyon dönmese bile bu örneğin bir kapak oluşturduğunun kanıtı

* AC # sorusu


11
ExternalFunction döndükten sonra kapanışın kullanımı hakkındaki yorumun bir kısmını ele almasa da, bir kapatma örneği olarak kod "doğrudur". Yani bu harika bir örnek değil. İç İşlevin döndürülmesini gerektirmeyen bir kapatma yönteminin kullanılabileceği birçok yol vardır. örneğin innerFunction hemen çağrıldığı veya saklandığı ve bir süre sonra çağrıldığı başka bir işleve geçirilebilir ve her durumda çağrıldığında oluşturulan outerFunction bağlamına erişimi vardır.
dlaliberte

6
@syockit Hayır, Moss yanlış. İşlevin tanımlandığı kapsamdan kaçıp kaçmadığına bakılmaksızın bir kapatma oluşturulur ve ebeveynin sözlü ortamına koşulsuz olarak oluşturulan bir başvuru, ana kapsamdaki tüm değişkenleri, dışarıdan veya içeriden çağrılmaksızın tüm işlevler için kullanılabilir hale getirir. oluşturuldukları kapsam.
Asad Saeeduddin

176

GOOD / BAD karşılaştırmalarıyla daha iyi öğrenme eğilimindeyim. Birinin karşılaşabileceği çalışma kodunu ve ardından çalışmayan kodu izlemeyi seviyorum. Bir karşılaştırma yapan ve fark edebileceğim en basit açıklamalarla arasındaki farkları azaltmaya çalışan bir jsFiddle'ı bir araya getirdim .

Doğru yapılan kapanışlar:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • Yukarıdaki kodda createClosure(n), döngünün her yinelemesinde çağrılır. Değişkenin n, yeni bir işlev kapsamında oluşturulan yeni bir değişken indexolduğunu ve dış kapsama bağlı olanla aynı olmadığını vurgulamak için adlandırdığımı unutmayın .

  • Bu yeni bir kapsam oluşturur ve nbu kapsama bağlıdır; Bu, her bir yineleme için bir tane olmak üzere 10 ayrı kapsamımız olduğu anlamına gelir.

  • createClosure(n) bu kapsamdaki n değerini döndüren bir işlev döndürür.

  • Her kapsamda n, createClosure(n)çağrıldığı zaman sahip olduğu değere bağlıdır, böylece döndürülen iç içe işlev her nzaman createClosure(n)çağrıldığında sahip olduğu değeri döndürür .

Kapanışlar yanlış yapıldı:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • Yukarıdaki kodda, döngü createClosureArray()fonksiyon içinde taşındı ve fonksiyon şimdi tamamlanmış diziyi döndürüyor, ki bu ilk bakışta daha sezgisel görünüyor.

  • Açıkça görülemeyen şey, createClosureArray()bu işlev için, döngü her yinelemesi için bir yerine yalnızca bir kapsam yaratıldığı için açık bir şekilde çağrılabilir.

  • Bu işlev içinde adlı bir değişken indextanımlanır. Döngü çalışır ve dönen diziye işlevler ekler index. Sadece bir kez çağrılan fonksiyon indexiçinde tanımlandığını unutmayın createClosureArray.

  • Çünkü createClosureArray()fonksiyon içinde sadece bir kapsam vardı, sadece o kapsam içindeki indexbir değere bağlı. Başka bir deyişle, döngü değerini indexher değiştirdiğinde, onu bu kapsamda ona başvuran her şey için değiştirir.

  • Diziye eklenen tüm işlevler index, ilk örnek gibi 10 farklı kapsamdan 10 farklı işlev yerine SAME değişkenini tanımlandığı üst kapsamdan döndürür . Sonuç olarak, 10 işlevin tümü aynı değişkeni aynı kapsamdan döndürür.

  • Döngü bittikten ve indexdeğiştirildikten sonra son değer 10'du, bu nedenle diziye eklenen her işlev indexşimdi 10'a ayarlanan tek değişkenin değerini döndürür .

Sonuç

KAPATMA YAPILDI SAĞ
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

YANLIŞ KAPANLAR
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


1
Güzel bir ek, teşekkürler. Daha açık bir şekilde ifade etmek gerekirse, "kötü" dizinin "kötü" döngüde her yinelemeyle nasıl oluşturulduğunu hayal edebiliyoruz: 1. yineleme: [function () {return 'n =' + 0;}] 2. yineleme: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3. yineleme: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] vb. Böylece, dizin değeri her değiştiğinde tüm işlevlere yansır diziye zaten eklenmiş.
Alex Alexeev

3
Kullanılması letiçin vardüzeltmelerin fark.
Rupam Datta

Burada "Kapanış Doğru Yapıldı" "Kapanış İçinde Kapanış" ın bir örneği değil mi?
TechnicalSmile

Demek istediğim, her fonksiyon teknik olarak bir kapanıştır, ancak önemli olan, fonksiyonun içinde yeni bir değişken tanımlamasıdır. Getiren işlev, yalnızca nyeni bir kapakta oluşturulan başvuruları döndürür . Diziye depolayabilmemiz ve daha sonra çalıştırabilmemiz için bir işlev döndürüyoruz.
Chev

Sadece birinci tekrarda dizide sonucu saklamak istiyorsanız o zaman bu gibi satır içi olabilir: arr[index] = (function (n) { return 'n = ' + n; })(index);. Ama sonra ortaya çıkan dizeyi benim örneğimin noktasını yenen bir işlev yerine dizide saklıyorsunuz.
Chev

164

Kapaklarda Wikipedia :

Bilgisayar biliminde kapatma, bu işlevin yerel olmayan isimleri (serbest değişkenler) için bir referans ortamıyla birlikte bir işlevdir.

Teknik olarak, içinde JavaScript , her işlev bir kapaktır . Her zaman çevredeki kapsamda tanımlanan değişkenlere erişime sahiptir.

Yana JavaScript kapsam tanımlayan inşaat bir fonksiyondur , birçok başka dillerde olduğu gibi bir kod bloğu, genellikle ne demek kapatılması JavaScript bir olduğunu zaten idam çevreleyen fonksiyonu tanımlanan yerel olmayan değişkenlerle fonksiyon çalışma .

Kapaklar genellikle bazı gizli özel verilerle işlevler oluşturmak için kullanılır (ancak her zaman böyle değildir).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

Yukarıdaki örnek, bir kez yürütülen anonim bir işlevi kullanmaktır. Ama olmak zorunda değil. Bu mkdbher çağrıldığında bir veritabanı işlevi oluşturarak adlandırılabilir (örneğin ) ve daha sonra yürütülebilir. Oluşturulan her işlevin kendi gizli veritabanı nesnesi olacaktır. Kapakların başka bir kullanım örneği, bir işlev döndürmediğimiz, ancak farklı amaçlar için birden çok işlev içeren bir nesne döndürdüğümüzde, bu işlevlerin her biri aynı verilere erişebilir.


2
JavaScript kapanışları için en iyi açıklama budur. Seçilen cevap olmalı. Gerisi yeterince eğlenceli ama bu gerçek dünyadaki JavaScript kodlayıcıları için pratik olarak kullanışlı.
geoidesic

136

Kapakların nasıl çalıştığını açıklamak için etkileşimli bir JavaScript eğitimi hazırladım. Kapanış nedir?

İşte örneklerden biri:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

128

Çocuklar, ebeveynleri gittikten sonra bile, ebeveynleriyle paylaştıkları sırları her zaman hatırlayacaklardır. Fonksiyonlar için kapaklar budur.

JavaScript işlevlerinin sırları özel değişkenlerdir

var parent = function() {
 var name = "Mary"; // secret
}

Her aradığınızda yerel değişken "name" oluşturulur ve "Mary" adı verilir. Ve fonksiyon her çıktığında değişken kaybolur ve isim unutulur.

Tahmin edebileceğiniz gibi, fonksiyonlar her çağrıldığında değişkenler yeniden yaratıldığından ve başka hiç kimse bunları bilmeyeceğinden, saklandıkları gizli bir yer olmalıdır. Sırlar Odası veya yığın veya yerel kapsam olarak adlandırılabilir , ancak gerçekten önemli değil. Orada, bir yerde, hafızada gizlenmiş olduklarını biliyoruz.

Ancak, JavaScript'te, diğer işlevlerin içinde oluşturulan, ebeveynlerinin yerel değişkenlerini de bilen ve yaşadıkları sürece bunları tutabilen çok özel bir şey vardır.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Böylece, üst işlevde olduğumuz sürece, gizli değişkenleri gizli yerden paylaşan bir veya daha fazla alt işlev oluşturabilir.

Ancak üzücü olan şey, eğer çocuk aynı zamanda ebeveyn işlevinin özel bir değişkeni ise, ebeveyn sona erdiğinde de ölecek ve sırlar onlarla birlikte ölecektir.

Yaşamak için çocuk çok geç olmadan ayrılmak zorunda

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

Ve şimdi, Mary artık "koşmuyor" olsa da, onun anısı kaybolmuyor ve çocuğu her zaman birlikte adını ve paylaştıkları diğer sırları hatırlayacak.

Yani, çocuğa "Alice" derseniz,

child("Alice") => "My name is Alice, child of Mary"

Söyleyeceklerin hepsi bu kadar.


15
Bu benim için en anlamlı olan açıklamadır çünkü teknik terimler hakkında önemli bir ön bilgi sahibi değildir. Burada en çok oylanan açıklama, kapanışları anlamayan kişinin 'sözlüksel kapsam' ve 'yürütme bağlamı' gibi terimleri tam ve eksiksiz bir şekilde anladığını varsayar - bunları kavramsal olarak anlayabiliyorum, ancak olması gerektiği gibi detayları ile rahat ve içinde hiçbir jargon ile açıklama hiç kapanış nihayet benim için tıklayın, teşekkür ederim. Bir bonus olarak, hangi kapsamın çok kısaca olduğunu da açıklıyor.
Emma W

103

Cevapların burada neden bu kadar karmaşık olduğunu anlamıyorum.

İşte bir kapanış:

var a = 42;

function b() { return a; }

Evet. Muhtemelen bunu günde birçok kez kullanıyorsunuz.


Kapakların belirli sorunları ele almak için karmaşık bir tasarım hacki olduğuna inanmak için hiçbir neden yoktur. Hayır, kapanışlar sadece işlevin bildirildiği (çalıştırılmadığı) açısından daha yüksek bir kapsamdan gelen bir değişkeni kullanmakla ilgilidir .

Şimdi yapmanıza izin verdiği şey daha muhteşem olabilir, diğer cevaplara bakın.


5
Bu cevabın insanların güvensizliğine yardımcı olması muhtemel görünmüyor. Geleneksel programlama dilinde bir kaba eşdeğer bir nesne üzerinde bir yöntem olarak () b oluşturmak için olabilir ayrıca özel sabit veya özelliğine sahiptir a. Aklıma sürpriz, JS kapsam nesnesinin etkin abir sabit yerine bir özellik olarak sağlamaktır. Ve bu önemli davranışı sadece değiştirirseniz fark edeceksiniz, çünküreturn a++;
Jon Coombs

1
Kesinlikle Jon ne dedi. Sonunda kapanışları kapatmadan önce pratik örnekler bulmakta zorlandım. Evet, floribon bir kapanış yarattı, ama beni eğitmemiş olmak için bu bana kesinlikle hiçbir şey öğretmezdi.
Chev

3
Bu, bir kapamanın ne olduğunu tanımlamaz - sadece bir kapıyı kullanan bir örnektir. Ve kapsam sona erdiğinde ne olduğuna dair nüansı ele almaz; Tüm kapsamlar hala etrafta ve özellikle küresel bir değişken söz konusu olduğunda kimsenin sözlük kapsamı hakkında bir sorusu olduğunu sanmıyorum.
Gerard ONeill

91

Dlaliberte tarafından ilk noktaya örnek:

Kapatma yalnızca bir iç işlevi döndürdüğünüzde oluşturulmaz. Aslında, kapalı fonksiyonun geri dönmesine gerek yoktur. Bunun yerine, iç fonksiyonunuzu bir dış kapsamdaki bir değişkene atayabilir veya hemen kullanılabileceği başka bir fonksiyona argüman olarak iletebilirsiniz. Bu nedenle, herhangi bir iç işlev çağrıldığı anda ona erişebildiğinden, kapatma işlevinin kapatılması muhtemelen kapatma işlevinin çağrıldığı anda zaten vardır.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

Olası bir belirsizlik hakkında küçük açıklama. "Aslında, çevreleme işlevinin geri dönmesine gerek yok." "Geri dönüş yok" demek istemedim ama "hala aktif". Dolayısıyla örnek bu yönü göstermese de, iç fonksiyonun dış kapsama geçirilebileceği başka bir yol olduğunu gösterir. Yapmaya çalıştığım ana nokta , kapatma işlevi (çevreleme işlevi için) oluşturma zamanıdır , çünkü bazı insanlar çevirme işlevi döndüğünde bunun olduğunu düşünmektedir. Bir işlev çağrıldığında kapağın oluşturulduğunu göstermek için farklı bir örnek gerekir .
dlaliberte

88

Kapatma, bir iç işlevin dış işlevindeki değişkenlere erişebildiği yerdir. Muhtemelen kapanışlar için alabileceğiniz en basit tek satırlık açıklama.


35
Bu açıklamanın sadece yarısı. Kapaklar hakkında dikkat edilmesi gereken önemli nokta, eğer dış fonksiyon çıktıktan sonra hala iç fonksiyondan bahsediliyorsa, dış fonksiyonun eski değerleri hala iç fonksiyona açıktır.
pcorcoran

22
Aslında, iç fonksiyon için mevcut olan dış fonksiyonun eski değerleri değil, bazı fonksiyonlar bunları değiştirebildiyse yeni değerlere sahip olabilecek eski değişkenlerdir .
dlaliberte

86

Zaten çok sayıda çözüm olduğunu biliyorum, ancak bu küçük ve basit komut dosyasının konsepti göstermek için yararlı olabileceğini düşünüyorum:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

82

Üzerinde uyuyorsun ve Dan'ı davet ediyorsun. Dan'a bir XBox denetleyicisi getirmesini söylersiniz.

Dan, Paul'ü davet eder. Dan Paul'den bir kontrolör getirmesini ister. Partiye kaç kontrolör getirildi?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

80

Closures'ın yazarı, kapanışları oldukça iyi açıkladı, onlara neden ihtiyacımız olduğunu açıkladı ve kapanışları anlamak için gerekli olan LexicalEnvironment'ı açıkladı.
İşte özet:

Bir değişkene erişilir ancak yerel değilse ne olur? Burası gibi:

Resim açıklamasını buraya girin

Bu durumda, yorumlayıcı değişkeni dış LexicalEnvironmentnesnede bulur .

Süreç iki adımdan oluşur:

  1. İlk olarak, bir işlev f yaratıldığında, boş bir alanda yaratılmaz. Geçerli bir LexicalEnvironment nesnesi var. Yukarıdaki durumda, bu penceredir (a, işlev oluşturma sırasında tanımlanmamıştır).

Resim açıklamasını buraya girin

Bir işlev oluşturulduğunda, geçerli LexicalEnvironment'a başvuran [[Scope]] adlı gizli bir özellik alır.

Resim açıklamasını buraya girin

Bir değişken okunur ancak hiçbir yerde bulunamazsa, bir hata oluşur.

İç içe işlevler

İşlevler, bir kapsam zinciri olarak da adlandırılabilecek bir LexicalEnvironments zinciri oluşturarak birbirlerinin içine yerleştirilebilir.

Resim açıklamasını buraya girin

Böylece, g fonksiyonunun g, a ve f'ye erişimi vardır.

Kapaklar

Dış işlev tamamlandıktan sonra iç içe geçmiş bir işlev yaşamaya devam edebilir:

Resim açıklamasını buraya girin

LexicalEnvironments'ı işaretleme:

Resim açıklamasını buraya girin

Gördüğümüz gibi this.say, kullanıcı nesnesindeki bir özellik olduğundan, Kullanıcı tamamlandıktan sonra yaşamaya devam eder.

Ve hatırlarsanız, ne zaman this.sayoluşturulduğunu, (her işlev gibi) this.say.[[Scope]]geçerli LexicalEnvironment için dahili bir referans alır . Bu nedenle, geçerli Kullanıcı yürütme işleminin LexicalEnvironment bellekte kalır. Kullanıcının tüm değişkenleri aynı zamanda onun özellikleridir, bu nedenle dikkatlice tutulurlar, genellikle önemsiz değildirler.

Bütün mesele, eğer iç işlev gelecekte bir dış değişkene erişmek istiyorsa, bunu yapabilmesini sağlamaktır.

Özetlemek:

  1. İç fonksiyon dış LexicalEnvironment referansını tutar.
  2. İç fonksiyon, dış fonksiyon bitmiş olsa bile, herhangi bir zamanda değişkenlere erişebilir.
  3. Tarayıcı, ona başvuran bir iç işlev olana kadar LexicalEnvironment'ı ve tüm özelliklerini (değişkenlerini) bellekte tutar.

Buna kapanış denir.


78

JavaScript işlevleri şunlara erişebilir:

  1. Argümanlar
  2. Yereller (yerel değişkenleri ve yerel işlevleri)
  3. Aşağıdakileri içeren çevre:
    • DOM dahil globaller
    • dış fonksiyonlardaki herhangi bir şey

Bir işlev çevresine erişirse, işlev bir kapanıştır.

Dış işlevlerin gerekli olmadığını unutmayın, ancak burada bahsetmediğim faydalar sunuyorlar. Bir kapatma, ortamındaki verilere erişerek bu verileri canlı tutar. Dış / iç fonksiyonların alt çantasında, bir dış fonksiyon yerel veri oluşturabilir ve sonunda çıkabilir ve yine de, dış fonksiyon çıktıktan sonra herhangi bir iç fonksiyon (lar) hayatta kalırsa, iç fonksiyon (lar) dış fonksiyonun yerel verilerini tutar canlı.

Küresel ortamı kullanan bir kapatma örneği:

Yığın Taşması Oylama ve Oylama Aşağı düğmesi olaylarının, global olarak tanımlanan dış değişkenlere isVotedUp ve isVotedDown olan kapatma, voteUp_click ve voteDown_click olarak uygulandığını düşünün. (Basit olması için, Yanıt Oylama düğmelerinin dizisine değil, StackOverflow'un Soru Oylama düğmelerine atıfta bulunuyorum.)

Kullanıcı Oy Ver düğmesine tıkladığında voteUp_click işlevi, oylamayı kullanıp kullanmayacağınızı veya yalnızca aşağı oylamayı iptal edip etmeyeceğinizi belirlemek için isVotedDown == true olup olmadığını kontrol eder. VoteUp_click işlevi, ortamına eriştiği için bir kapanıştır.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Bu işlevlerin dördü de hepsi çevrelerine eriştikçe kapaklardır.


59

6 yaşında bir çocuğun babası olarak, şu anda küçük çocuklara (ve resmi bir eğitim olmadan kodlama için göreceli bir acemi olarak, düzeltmeler gerekli olacaktır), dersin pratik oyun yoluyla en iyi şekilde olacağını düşünüyorum. 6 yaşındaki bir çocuk kapamanın ne olduğunu anlamaya hazırsa, o zaman kendileri gidebilecek kadar yaşlılar. Kodu jsfiddle.net'e yapıştırmayı, biraz açıklamayı ve benzersiz bir şarkıyı uydurmak için onları yalnız bırakmayı öneriyorum. Aşağıdaki açıklayıcı metin muhtemelen 10 yaşındakiler için daha uygundur.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

TALİMATLAR

VERİ: Veriler bir olgu topluluğudur. Sayılar, kelimeler, ölçümler, gözlemler ve hatta sadece şeylerin açıklamaları olabilir. Dokunamaz, koklayamaz veya tadamazsınız. Yazabilir, konuşabilir ve duyabilirsiniz. Bir bilgisayar kullanarak dokunma kokusu ve tadı oluşturmak için kullanabilirsiniz . Kod kullanan bir bilgisayar tarafından kullanışlı hale getirilebilir.

KOD: Yukarıdaki tüm yazılara kod denir . JavaScript ile yazılmıştır.

JAVASCRIPT: JavaScript bir dildir. İngilizce veya Fransızca veya Çince gibi diller. Bilgisayarlar ve diğer elektronik işlemciler tarafından anlaşılan birçok dil vardır. JavaScript'in bir bilgisayar tarafından anlaşılması için bir tercümana ihtiyacı vardır. Sadece Rusça konuşan bir öğretmenin sınıfınıza okulda ders verip vermediğini düşünün. Öğretmen "все садятся" dediğinde, sınıf anlamıyordu. Ama neyse ki sınıfınızda herkese bunun "herkes oturması" anlamına gelen bir Rus öğrenciniz var - hepiniz öyle. Sınıf bir bilgisayar gibidir ve Rus öğrenci tercümandır. JavaScript için en yaygın yorumlayıcıya tarayıcı denir.

TARAYICI: Bir web sitesini ziyaret etmek için bir bilgisayar, tablet veya telefonda Internet'e bağlandığınızda, bir tarayıcı kullanırsınız. Bildiğiniz örnekler Internet Explorer, Chrome, Firefox ve Safari'dir. Tarayıcı JavaScript'i anlayabilir ve bilgisayara ne yapması gerektiğini söyleyebilir. JavaScript komutlarına işlevler denir.

FONKSİYON: JavaScript'teki bir işlev fabrika gibidir. İçinde sadece bir makine bulunan küçük bir fabrika olabilir. Ya da her biri farklı işler yapan birçok küçük fabrika içerebilir. Gerçek hayattaki bir giyim fabrikasında kumaş parçalarını ve iplik bobinlerini içeri sokabilirsiniz ve tişörtler ve kotlar çıkıyor. JavaScript fabrikamız yalnızca verileri işler, dikemez, delik açamaz veya metali eritemez. JavaScript fabrikamızda veriler giriyor ve veriler çıkıyor.

Tüm bu veri şeyler biraz sıkıcı geliyor, ama gerçekten çok havalı; bir robotun akşam yemeğinde ne yapması gerektiğini söyleyen bir fonksiyonumuz olabilir. Sizi ve arkadaşınızı evime davet ettiğimi varsayalım. En çok tavuk budu seviyorsun, sosisleri seviyorum, arkadaşın her zaman istediğini istiyor ve arkadaşım et yemiyor.

Alışverişe gitmek için zamanım yok, bu yüzden fonksiyonun karar vermek için buzdolabında ne olduğunu bilmemiz gerekiyor. Her malzemenin farklı bir pişirme süresi vardır ve her şeyin robot tarafından aynı anda sıcak servis edilmesini istiyoruz. Bu işleve, sevdiğimiz şeyle ilgili verileri sağlamalıyız, işlev buzdolabıyla 'konuşabilir' ve işlev robotu kontrol edebilir.

Bir işlevin normalde bir adı, parantezleri ve kaşlı ayraçları vardır. Bunun gibi:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Buna dikkat edin /*...*/ve //tarayıcı tarafından okunan kodu durdurun.

İSİM: Bir işlevi istediğiniz herhangi bir kelimeyle çağırabilirsiniz. "CookMeal" örneği, iki kelimeyi bir araya getirme ve ikincisine başlangıçta büyük harf verme konusunda tipiktir - ancak bu gerekli değildir. İçinde boşluk olamaz ve kendi başına bir sayı olamaz.

EBEVEYNLER: "Parantez" veya ()fabrikada bilgi paketleri göndermek için JavaScript işlevi fabrika kapısındaki mektup kutusu veya caddedeki posta kutusudur. Bazen postbox işaretlenebilir örneğin cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) bu durumda bunu vermek zorunda hangi verileri biliyorum.

BRACES: Buna benzeyen "Parantez" {}fabrikamızın renkli camlarıdır. Fabrikanın içinden görebilirsiniz, ama dışarıdan göremezsiniz.

ÜSTÜN UZUN KOD ÖRNEĞİ

Kodumuz word fonksiyonu ile başlar , bu yüzden bir tane olduğunu biliyoruz! Sonra şarkı işlevinin adı - bu benim işlevin ne hakkında kendi açıklamasım. Sonra parantez () . Parantezler her zaman bir işlev için vardır. Bazen boş, bazen de bir şey var Bu seferki bir kelime var.: (person). Bundan sonra böyle bir destek var {. Bu, sing () işlevinin başlangıcını işaretler . Bunun gibi sing () işaretinin sonunu gösteren bir ortağı var}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Bu nedenle, bu işlevin şarkı söyleme ile ilgisi olabilir ve bir kişi hakkında bazı verilere ihtiyaç duyabilir. İçinde bu verilerle bir şeyler yapmak için talimatlar var.

Şimdi, sing () işlevinden sonra , kodun sonuna yakın satır

var person="an old lady";

DEĞİŞKEN: harfler var "değişkeni" için standı. Değişken zarf gibidir. Dış tarafta bu zarf "kişi" olarak işaretlenmiştir. İçinde fonksiyonumuzun ihtiyaç duyduğu bilgileri içeren bir kâğıt bulunur, bazı harfler ve boşluklar, "yaşlı bir kadın" kelimesini okuyan bir cümle gibi bir dize parçası (dize denir) gibi bir araya gelir. Zarfımız sayılar (tamsayılar olarak adlandırılır), talimatlar (işlevler olarak adlandırılır), listeler ( diziler olarak adlandırılır) gibi başka şeyler de içerebilir . Bu değişken tüm parantezlerin dışında yazıldığından ve parantezlerin {}içindeyken renkli pencerelerden görebildiğiniz için, bu değişken kodun herhangi bir yerinden görülebilir. Buna 'küresel değişken' diyoruz.

KÜRESEL DEĞİŞKEN: kişi siz "genç bir adam" ile "yaşlı bir kadının", değerini değiştirirseniz, yani küresel bir değişkendir kişi tekrar değiştirmek için karar verene kadar genç bir adam olmaya devam edeceği anlamına da başka işlevi de kod genç bir adam olduğunu görebiliyor. F12Bir tarayıcının geliştirici konsolunu açmak için düğmeye basın veya Seçenekler ayarlarına bakın ve bu değerin ne olduğunu görmek için "person" yazın. Tür person="a young man"o değiştiğini görmek için tekrar "kişi" olarak değiştirin ve sonra yazın.

Bundan sonra çizgimiz var

sing(person);

Bu hat, sanki bir köpek çağırıyormuş gibi işlevi çağırıyor

"Haydi şarkı söyle , Gel ve kişi bul !"

Tarayıcı bu satıra ulaşan JavaScript kodunu yüklediğinde, işlevi başlatacaktır. Tarayıcının çalıştırmak için gereken tüm bilgilere sahip olduğundan emin olmak için satırı sonuna koydum.

Fonksiyonlar eylemleri tanımlar - ana fonksiyon şarkı söylemekle ilgilidir. FirstPart adında, şarkının her ayetine uygulanan kişi hakkındaki şarkıya uygulanan bir değişken içerir : "Yutulan" + kişi + "vardı. Eğer yazarsanız firstPart konsoluna değişken bir fonksiyonu kilitli olduğu için, cevap almazsınız - Tarayıcı parantez koyu renk camlarında içine göremez.

KAPATLAR: Kapaklar, büyük şarkı () işlevinin içindeki daha küçük işlevlerdir. Büyük fabrika içindeki küçük fabrikalar. Her birinin kendi parantezleri vardır, bu da içindeki değişkenlerin dışarıdan görülemeyeceği anlamına gelir. Bu yüzden değişkenlerin isimleri ( yaratık ve sonuç ) kapaklarda ancak farklı değerlerle tekrarlanabilir. Bu değişken adlarını konsol penceresine yazarsanız, değerini iki kat renkli pencere tarafından gizlendiği için almazsınız.

Kapakların hepsi , firstPart adlı sing () işlevinin değişkeninin ne olduğunu bilir , çünkü renkli pencerelerinden görebilirler.

Kapanışlardan sonra çizgiler geliyor

fly();
spider();
bird();
cat();

Sing () işlevi, bu işlevlerin her birini verildikleri sırayla çağırır. Sonra sing () fonksiyonunun çalışması yapılacaktır.


56

Tamam, 6 yaşında bir çocukla konuşurken, muhtemelen aşağıdaki dernekleri kullanırdım.

Hayal edin - tüm evde küçük kardeşlerinizle oynuyorsunuz ve oyuncaklarınızla dolaşıyorsunuz ve bazılarını abinizin odasına getirdiniz. Bir süre sonra kardeşiniz okuldan döndü ve odasına gitti ve içine kilitlendi, böylece artık orada bırakılan oyuncaklara doğrudan bir şekilde erişemediniz. Ama kapıyı çalabilir ve kardeşinden bu oyuncakları isteyebilirsin. Buna toy'un kapatılması denir ; kardeşin senin için uydurdu ve o şimdi dışarda .

Bir kapının taslak tarafından kilitlendiği ve içeride kimsenin olmadığı (genel işlev yürütme) bir durumla karşılaştırın ve daha sonra bazı yerel yangınlar meydana gelir ve odayı yakar (çöp toplayıcı: D) ve sonra yeni bir oda inşa edildi ve şimdi ayrılabilirsiniz başka bir oyuncak var (yeni fonksiyon örneği), ancak ilk oda örneğinde bırakılan aynı oyuncakları asla alamazlar.

Gelişmiş bir çocuk için aşağıdakine benzer bir şey koyardım. Mükemmel değil, ama ne olduğunu hissettiriyor:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Gördüğünüz gibi, odaya bırakılan oyuncaklara kardeş aracılığıyla erişilebilir ve oda kilitli olursa olsun. İşte onunla oynamak için bir jsbin .


49

Altı yaşında bir çocuğun cevabı (bir fonksiyonun ne olduğunu ve bir değişkenin ne olduğunu ve hangi verilerin olduğunu bildiğini varsayarak):

İşlevler veri döndürebilir. Bir işlevden döndürebileceğiniz bir tür veri başka bir işlevdir. Bu yeni işlev döndürüldüğünde, onu oluşturan işlevde kullanılan tüm değişkenler ve bağımsız değişkenler kaybolmaz. Bunun yerine, bu üst işlev "kapanır". Başka bir deyişle, hiçbir şey onun içine bakamaz ve döndürdüğü işlev dışında kullandığı değişkenleri göremez. Bu yeni işlevin, onu oluşturan işlevin içine bakmak ve içindeki verileri görmek için özel bir yeteneği vardır.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Bunu açıklamanın bir başka basit yolu kapsam açısından:

Daha geniş bir kapsamın içinde daha küçük bir kapsam oluşturduğunuzda, daha küçük kapsam her zaman daha büyük kapsamın içinde ne olduğunu görebilir.


49

JavaScript'teki bir işlev yalnızca bir dizi yönerge referansı değildir (C dilinde olduğu gibi), aynı zamanda kullandığı tüm yerel olmayan değişkenlere (yakalanan değişkenler) yapılan referanslardan oluşan gizli bir veri yapısı içerir. Bu iki parçalı işlevlere kapatma denir. JavaScript'teki her işlev bir kapatma olarak düşünülebilir.

Kapaklar durumlu işlevlerdir. "This" ifadesinin bir işlev için durum sağladığı ve "this" ifadesinin ayrı nesneler olduğu anlamında "this" ifadesine biraz benzemektedir ("this" sadece süslü bir parametredir ve onu kalıcı olarak bir cihaza bağlamanın tek yolu işlevi bir kapatma oluşturmaktır). "Bu" ve işlev her zaman ayrı ayrı yaşarken, bir işlev kapatılmasından ayrılamaz ve dil yakalanan değişkenlere erişmek için hiçbir yol sağlamaz.

Sözcüksel olarak iç içe geçmiş bir işlev tarafından başvurulan tüm bu dış değişkenler, aslında sözcüksel olarak çevreleyen işlevler zincirindeki yerel değişkenlerdir (genel değişkenlerin bazı kök işlevlerinin yerel değişkenleri olduğu varsayılabilir) ve bir işlevin her bir tekrarı, yerel değişkenleri, bir işlevi döndüren (veya geri çağırma olarak kaydetme gibi bir işlevi başka bir şekilde aktaran) iç içe bir işlevin, yeni bir kapatma oluşturduğunu (yürütülmesini temsil eden kendine özgü benzersiz referanslı yerel olmayan değişkenler kümesiyle) izler. bağlamı).

Ayrıca, JavaScript'teki yerel değişkenlerin yığın çerçevesinde değil, yığın üzerinde yaratıldığı ve yalnızca kimse bunlara başvurmadığında yok edildiği anlaşılmalıdır. Bir işlev döndüğünde, yerel değişkenlerine yapılan başvurular azaltılır, ancak geçerli yürütme sırasında bir kapatma işleminin parçası haline geldiyse ve yine de sözcük olarak iç içe geçmiş işlevleri tarafından başvuruda bulunulursa yine de null olmayabilirler. bu yuvalanmış işlevler döndürülmüş veya başka bir dış koda başka şekilde aktarılmıştır).

Bir örnek:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

47

Belki de altı yaşındakilerden en büyüğü hariç her şeyden biraz daha fazlası, ancak JavaScript'te kapanma kavramının yapılmasına yardımcı olan birkaç örnek benim için tıklayın.

Kapatma, başka bir işlevin kapsamına (değişkenleri ve işlevleri) erişimi olan bir işlevdir. Kapatma oluşturmanın en kolay yolu, bir işlev içindeki bir işlevdir; bunun nedeni JavaScript'te bir işlevin her zaman içerdiği işlevin kapsamına erişebilmesidir.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

UYARI: maymun

Yukarıdaki örnekte, bunun dışında innerFunction öğesini çağıran externalFunction çağrılır. OuterVar değerinin doğru şekilde uyarılarak kanıtlandığı için innerVunction'un innerVunction için nasıl kullanılabilir olduğuna dikkat edin.

Şimdi aşağıdakileri göz önünde bulundurun:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

UYARI: maymun

referenceToInnerFunction, innerFunction öğesine yalnızca bir başvuru döndüren outerFunction () olarak ayarlanır. ReferenceToInnerFunction çağrıldığında outerVar öğesini döndürür. Yine, yukarıdaki gibi, bu innerFunction öğesinin outerFunction değişkeni olan outerVar'a erişimi olduğunu gösterir. Ayrıca, outerFunction yürütmeyi bitirdikten sonra bile bu erişimi koruduğunu belirtmek ilginçtir.

Ve işlerin gerçekten ilginç hale geldiği yer burası. OuterFunction öğesinden kurtulursak, null olarak ayarla diyelim, referenceToInnerFunction öğesinin externalVar değerine erişimini kaybedeceğini düşünebilirsiniz. Ancak durum böyle değil.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

UYARI: maymun UYARI: maymun

Ama bu nasıl? ReferenceToInnerFunction, externalFarn null olarak ayarlandığına göre, outerVar'ın değerini hala nasıl bilebilir?

ReferenceToInnerFunction öğesinin hala outerVar değerine erişebilmesinin nedeni, closure'un functionFonksiyonu outerFunction içine yerleştirerek ilk oluşturulduğu zaman, innerFunction'un outerFunction'ın kapsamına (değişkenleri ve işlevleri) kendi kapsam zincirine bir referans eklemesidir. Bunun anlamı, innerFunction öğesinin outerVar da dahil olmak üzere outerFunction değişkenlerinin tümüne bir işaretçi veya referans olmasıdır. Böylece, outerFunction yürütmeyi bitirdiğinde veya silinmiş ya da null değerine ayarlanmış olsa bile, outerVar gibi kapsamındaki değişkenler, innerFunction öğesinin döndürülen kısmında olağanüstü bir referans olması nedeniyle bellekte kalır. referenceToInnerFunction. OuterVar'ı ve outerFunction değişkenlerinin geri kalanını gerçekten bellekten serbest bırakmak için, bu olağanüstü referanstan kurtulmanız gerekir,

//////////

Dikkat edilmesi gereken kapaklar hakkında iki şey daha var. İlk olarak, kapatma her zaman içerme işlevinin son değerlerine erişebilir.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

UYARI: goril

İkincisi, bir kapatma oluşturulduğunda, çevreleyen işlevinin tüm değişkenlerine ve işlevlerine bir referans tutar; seçmek ve seçmek mümkün değil. Ve ancak, bu nedenle, bellek yoğun olabileceğinden, kapaklar az veya en azından dikkatlice kullanılmalıdır; bir çok değişken, bir fonksiyonun yürütülmesi bittikten çok sonra hafızada tutulabilir.


45

Onları sadece Mozilla Kapanışları sayfasına yönlendirirdim . Bulduğum kapatma temelleri ve pratik kullanımın en iyi, en özlü ve basit açıklaması . JavaScript öğrenen herkese şiddetle tavsiye edilir.

Ve evet, 6 yaşında bir çocuğa bile tavsiye ederim - 6 yaşındakiler kapanışları öğreniyorsa , makalede sağlanan özlü ve basit açıklamayı anlamaya hazır oldukları mantıklıdır .


Kabul ediyorum: söz konusu Mozilla sayfası özellikle basit ve özlü. Şaşırtıcı derecede mesajınız diğerleri kadar çok takdir edilmedi.
Brice Coustillas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.