'Kapanış' nedir?


432

Currying hakkında bir soru sordum ve kapaklardan bahsedildi. Kapanış nedir? Körelme ile ilişkisi nedir?


22
Şimdi kapatma tam olarak nedir ??? Bazı cevaplar kapanışın fonksiyon olduğunu söylüyor. Bazıları bunun yığın olduğunu söylüyor. Bazı cevaplar "gizli" değer olduğunu söylüyor. Anladığım kadarıyla, fonksiyon + kapalı değişkenler.
Roland

3
Bir kapanışın ne olduğunu açıklar: stackoverflow.com/questions/4103750/…
dietbuddha

Ayrıca bir kapama nedir? softwareengineering.stackexchange de
B12Toaster

Kapatma ve ortak kullanım durumunun ne olduğunu açıklar: trungk18.com/experience/javascript-closure
Sasuke91

Yanıtlar:


743

Değişken kapsam

Yerel bir değişken bildirdiğinizde, bu değişkenin bir kapsamı vardır. Genellikle, yerel değişkenler yalnızca bildirdiğiniz blok veya işlev içinde bulunur.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

Yerel bir değişkene erişmeye çalışırsam, çoğu dil geçerli kapsamda, daha sonra kök kapsama ulaşana kadar üst kapsamlar arasından arar.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

Bir blok veya işlev tamamlandığında, yerel değişkenlerine artık gerek yoktur ve genellikle bellek dışına çıkar.

Normalde işlerin böyle çalışmasını bekliyoruz.

Kapatma, kalıcı bir yerel değişken kapsamıdır

Kapatma, kod yürütme bu bloktan çıktıktan sonra bile yerel değişkenleri tutan kalıcı bir kapsamdır. Kapatmayı destekleyen diller (JavaScript, Swift ve Ruby gibi), bir referans tutmanız şartıyla, bu değişkenlerin bildirildiği bloğun tamamlanması bittikten sonra bile, bir kapsama (üst kapsamları dahil) bir referans göndermenize olanak tanır. o blok ya da bir yerde işlev.

Scope nesnesi ve tüm yerel değişkenleri işleve bağlıdır ve işlev devam ettiği sürece devam eder.

Bu bize fonksiyon taşınabilirliği sağlar. İşlevi tamamen tanımlandığımızda bile, işlev ilk tanımlandığında kapsamda olan değişkenlerin, daha sonra işlevi çağırdığımızda hala kapsamda olmasını bekleyebiliriz.

Örneğin

İşte JavaScript'te konuyu gösteren basit bir örnek:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Burada bir fonksiyon içinde bir fonksiyon tanımladım. İç fonksiyon, dahil olmak üzere tüm dış fonksiyonun yerel değişkenlerine erişim kazanır a. Değişken aiç fonksiyonun kapsamı içindedir.

Normalde bir işlevden çıkıldığında tüm yerel değişkenleri uçurulur. Ancak, iç işlevi döndürüp çıktıktan fncsonra da devam etmesi için bir değişkene outeratarsak, tanımlandığı zaman kapsamdaki tüm değişkenler innerde devam eder . Değişken akapatıldı - bir kapanış içinde.

Değişkenin atamamen özel olduğunu unutmayın fnc. Bu, JavaScript gibi işlevsel bir programlama dilinde özel değişkenler oluşturmanın bir yoludur.

Tahmin edebileceğiniz gibi, dediğimde "1" fnc()değerini yazdırır a.

Kapanmasız bir dilde, değişken aişlevden outerçıkıldığında çöp toplanır ve atılır . Fnc çağrıldığında aartık var olmadığı için hata oluştu .

JavaScript'te, değişken a, işlev ilk bildirildiğinde değişken kapsamı oluşturulduğu ve işlev devam ettiği sürece devam ettiği için değişmez.

akapsamına girer outer. Kapsamının kapsamına ilişkin innerbir üst işaretçi vardır outer. fncişaret eden bir değişkendir inner. adevam ettiği sürece fncdevam eder. akapanış içinde.


116
Bunun oldukça iyi ve anlaşılması kolay bir örnek olduğunu düşündüm.
user12345613

16
Harika açıklama için teşekkürler, birçok gördüm ama bu gerçekten anladım zamanı.
Dimitar Dimitrov

2
Bunun 2. ve son paragraflarda belirtildiği gibi JQuery gibi bir kütüphanede nasıl çalıştığına dair bir örnek alabilir miyim? Bunu tam olarak anlamadım.
DPM

6
Merhaba Jubbat, evet, jquery.js'yi açın ve ilk satıra bir göz atın. Bir fonksiyonun açıldığını göreceksiniz. Şimdi sonuna atlayın, window.jQuery = window. $ = JQuery öğesini göreceksiniz. Daha sonra fonksiyon kapatılır ve kendi kendine yürütülür. Artık $ işlevine erişiminiz var, bu da kapanışta tanımlanan diğer işlevlere erişime sahip. senin sorunun cevabı bu mu?
superluminary

4
Web üzerinde en iyi açıklama. Düşündüğümden çok daha basit
Mantis

95

Bir örnek vereceğim (JavaScript'te):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Bu işlev olan makeCounter, x olarak adlandırdığımız ve her çağrıldığında bir sayılan bir işlev döndürür. X için herhangi bir parametre sağlamadığımızdan, bir şekilde sayımı hatırlaması gerekir. Sözcüksel kapsam belirleme olarak adlandırılan yere göre nerede bulacağını bilir - değeri bulmak için tanımlandığı noktaya bakmalıdır. Bu "gizli" değer, kapatma olarak adlandırılan değerdir.

İşte yine benim köri örneğim:

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

var add3 = add(3);

add3(4); returns 7

Gördüğünüz şey, add parametresini a (3 olan) ile çağırdığınızda, bu değerin add3 olarak tanımladığımız döndürülen fonksiyonun kapanışında bulunmasıdır. Bu şekilde, add3 dediğimizde, ekleme işlemini gerçekleştirmek için a değerini nerede bulacağını bilir.


4
IDK, yukarıdaki dilde hangi dili (muhtemelen F #) kullandınız. Lütfen sözde kodda yukarıdaki örneği verebilir misiniz? Bunu anlamak için zor zamanlar geçiriyorum.
kullanıcı


3
@KyleCronin Harika bir örnek, teşekkürler. S: "Gizli değere kapatma adı verilir" veya "değeri gizleyen işlev kapatma" demek daha doğru mu? Veya "değeri gizleme süreci kapanıştır" mı? Teşekkürler!

2
@RobertHume Güzel soru. Anlamsal olarak, "kapatma" terimi biraz belirsizdir. Benim kişisel tanımım, hem gizli değerin hem de onu içine alan fonksiyonun kombinasyonunun kapanışı oluşturmasıdır.
Kyle Cronin

1
@KyleCronin Teşekkürler - Pazartesi gün ortası programım var. :) Kafamda "kapatma" kavramının sağlam olmasını istedim. OP'nin sorusuna bu harika yanıtı gönderdiğiniz için teşekkür ederiz!

58

Kyle'ın cevabı oldukça iyi. Ben sadece ek açıklama kapanış temelde lambda işlevi oluşturulduğu noktada yığının bir anlık görüntü olduğunu düşünüyorum. Daha sonra işlev yeniden yürütüldüğünde yığın, işlevi yürütmeden önce bu duruma geri yüklenir. Böylece Kyle'ın belirttiği gibi, bu gizli değer ( count) lambda işlevi yürütüldüğünde kullanılabilir.


14
Sadece yığın değil, aynı zamanda yığınta veya yığınta (veya her ikisinde) depolanıp saklanmadığına bakılmaksızın korunan sözcüksel kapsam (lar).
Matt Fenwick

38

Her şeyden önce, buradaki insanların çoğunun size söylediklerinin aksine, kapanma bir işlev değildir ! Peki olduğunu o?
Bu bir dizi (kendi olarak bilinen bir fonksiyonun "Çevre bağlamda" de tanımlanan sembollerin ortamında bu (olduğunu, her sembol tanımlanır ve değeri olan bir ekspresyon, bu yüzden değerlendirilebilir) bir KAPALI yansıtması).

Örneğin, bir JavaScript işleviniz olduğunda:

function closed(x) {
  return x + 3;
}

Bir olan , kapalı ifadesi içinde meydana gelen tüm semboller içinde tanımlanır çünkü bunu değerlendirmek, böylece (anlamları açıktır). Başka bir deyişle, bağımsızdır .

Ancak böyle bir işleviniz varsa:

function open(x) {
  return x*y + 3;
}

bir olduğunu açık bir ifade içinde tanımlanmamıştır içinde sembol vardır, çünkü. Yani y,. Bu işleve bakarken, ne yolduğunu ve ne anlama geldiğini söyleyemeyiz , değerini bilmiyoruz, bu yüzden bu ifadeyi değerlendiremeyiz. Yani içinde ne ydemek istediğini söyleyene kadar bu işlevi çağıramayız . Buna serbest değişkeny denir .

Bu ybir tanım için yalvarır, ancak bu tanım işlevin bir parçası değildir - "çevre bağlamında" ( çevre olarak da bilinir ) başka bir yerde tanımlanır . En azından umduğumuz şey bu: P

Örneğin, global olarak tanımlanabilir:

var y = 7;

function open(x) {
  return x*y + 3;
}

Veya onu saran bir işlevde tanımlanabilir:

var global = 2;

function wrapper(y) {
  var w = "unused";

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

Çevrenin bir ifadedeki serbest değişkenlere anlamlarını veren kısmı kapanmadır . Bu şekilde adlandırılır, çünkü açık bir ifadeyi tüm serbest değişkenleri için bu eksik tanımları sağlayarak kapalı bir ifadeye dönüştürür , böylece değerlendirebiliriz.

Yukarıdaki örnekte, (biz gerek çünkü bir isim vermedi) iç işlevi olan açık ifade değişken çünkü ybunun içinde serbest - tanımı bunu saran işlevinde, işlev dışında . Bu anonim işlevin ortamı , değişkenler kümesidir:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Şimdi kapanış , bu ortamın , tüm serbest değişkenleri için tanımları sağlayarak iç işlevi kapatan kısmıdır . Bizim durumumuzda, iç fonksiyondaki tek serbest değişken , bu fonksiyonun kapanması ortamının bu alt kümesidir:y

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Ortamda tanımlanan diğer iki sembol , bu işlevin kapanmasının bir parçası değildir , çünkü çalışmalarını gerektirmez. Onlar için gerekli olmayan kapatmak onu.

Bunun arkasındaki teori hakkında daha fazla bilgi: https://stackoverflow.com/a/36878651/434562

Yukarıdaki örnekte, sarma işlevinin iç işlevini bir değer olarak döndürdüğünü belirtmek gerekir. Bu fonksiyon dediğimiz an, fonksiyonun tanımlandığı (veya oluşturulduğu) andan itibaren uzaktan olabilir. Özellikle, sarma işlevi artık çalışmıyor ve çağrı yığınında bulunan parametreleri artık orada değil: P Bu bir sorun yaratıyor, çünkü iç işlev yçağrıldığında orada olması gerekiyor! Başka bir deyişle, kapanışından değişkenlerin sarma işlevini bir şekilde geride bırakmasını ve gerektiğinde orada olmasını gerektirir. Bu nedenle, iç işlev, bu değişkenlerin kapanmasını sağlayan ve daha sonra kullanılmak üzere güvenli bir yerde saklayan bir anlık görüntüsünü yapmak zorundadır . (Çağrı yığını dışında bir yerde.)

Ve bu yüzden insanlar sık ​​sık kapatma terimini , kullandıkları dış değişkenlerin bu tür anlık görüntülerini veya daha sonra kullanmak üzere bu değişkenleri saklamak için kullanılan veri yapısı olan özel bir işlev türü olarak karıştırırlar . Ama umarım şimdi bunların kendisinin kapanma olmadığını anlarsınız - sadece bir programlama dilinde kapanmaları uygulamanın yolları ya da fonksiyonun kapanmasından gelen değişkenlerin gerektiğinde orada olmasına izin veren dil mekanizmalarıdır. Kapanışların etrafında (gereksiz yere) bu konuyu gerçekte çok daha karmaşık ve karmaşık hale getiren birçok yanlış anlama var.


1
Yeni başlayanların buna yardımcı olabilecek bir benzetme, bir kapatma , tüm gevşek uçları bağlar , bu da bir kişinin kapanma aradığında yaptığı şeydir (veya gerekli tüm referansları çözer veya ...). Bu şekilde düşünmeme yardımcı oldu: o)
Crawford

Yıllar boyunca birçok kapanış tanımını okudum, ama bence bu benim favorim. Sanırım hepimiz böyle kavramları zihinsel olarak haritalamak için kendi yolumuz var ve bu benimkine çok benziyor.
Jason S.

29

Kapatma, başka bir işlevdeki duruma başvurabilen bir işlevdir. Örneğin, Python'da bu "iç" kapağını kullanır:

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

23

Kapakların anlaşılmasını kolaylaştırmak için, bunların prosedürel bir dilde nasıl uygulanabileceğini incelemek yararlı olabilir. Bu açıklama, Şema'daki kapakların basit bir uygulamasını izleyecektir.

Başlamak için bir ad alanı kavramını tanıtmalıyım. Şema yorumlayıcısına bir komut girdiğinizde, ifadedeki çeşitli sembolleri değerlendirmeli ve değerlerini almalıdır. Misal:

(define x 3)

(define y 4)

(+ x y) returns 7

Tanım ifadeleri, 3 değerini x için yerinde ve 4 değerini y için yerinde depolar. Sonra (+ xy) dediğimizde, yorumlayıcı isim alanındaki değerleri arar ve işlemi gerçekleştirir ve 7 değerini döndürür.

Ancak, Şema'da, bir sembolün değerini geçici olarak geçersiz kılmanıza izin veren ifadeler vardır. İşte bir örnek:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

Let anahtar sözcüğünün yaptığı, 5 değeri olarak x ile yeni bir ad alanı sunar. Y'nin 4 olduğunu görebildiğinizi ve toplamın 9 olarak döndüğünü fark edeceksiniz. İfadenin x ile bitmesinden sonra da görebilirsiniz. Bu anlamda x geçici olarak yerel değer tarafından maskelenmiştir.

Yordamsal ve nesne yönelimli diller benzer bir kavrama sahiptir. Genel değişkenle aynı ada sahip bir işlevde bir değişken bildirdiğinizde aynı etkiyi elde edersiniz.

Bunu nasıl uygularız? Bağlantılı bir listeyle basit bir yol vardır - kafa yeni değeri içerir ve kuyruk eski ad alanını içerir. Bir simgeye bakmanız gerektiğinde, kafadan başlayıp kuyruktan aşağı doğru ilerlersiniz.

Şimdi şimdilik birinci sınıf fonksiyonların uygulanmasına geçelim. Bir işlev az çok, işlev dönüş değerinde doruk noktasına çağrıldığında çalıştırılacak bir dizi talimattır. Bir fonksiyonda okuduğumuzda, bu talimatları sahne arkasında saklayabilir ve fonksiyon çağrıldığında bunları çalıştırabiliriz.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

X'i 3, artı-x'in parametresi, y artı x'in değeri olarak tanımlıyoruz. Son olarak, x'in yeni bir x tarafından maskelenmiş olduğu bir ortamda plus-x'i çağırırız, bu 5 değerindedir. Eğer bağlamda olduğumuzdan, artı-x işlevi için yalnızca (+ xy) işlemini saklarsak x'in 5 olması sonucu döndürülen sonuç 9 olur. Dinamik kapsam belirleme buna denir.

Bununla birlikte, Şema, Ortak Lisp ve diğer birçok dilde sözcük kapsamı oluşturma denir - işlemin depolanmasına ek olarak (+ xy), aynı zamanda ad alanını da saklarız. Bu şekilde, değerlere baktığımızda x'in bu bağlamda gerçekten 3 olduğunu görebiliriz. Bu bir kapanış.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

Özetle, işlev tanımlaması sırasında ad alanının durumunu saklamak için, bağlı kapsamlardan değişkenlere erişmemize izin vermenin yanı sıra, değişkenin geri kalanını etkilemeden bir değişkeni yerel olarak maskeleme yeteneğini sunmamıza izin veren bağlantılı bir liste kullanabiliriz. programı.


tamam, cevabın sayesinde, sonunda kapanmanın ne hakkında olduğu hakkında bir fikrim var. Ancak büyük bir soru var: "işlev tanımlaması sırasında ad alanının durumunu saklamak için bağlantılı bir liste kullanabiliriz, aksi takdirde artık kapsamda olmayacak değişkenlere erişmemize izin veririz." Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer

@Laser: Üzgünüm, bu cümlenin pek bir anlamı yoktu, bu yüzden güncelledim. Umarım şimdi daha mantıklıdır. Ayrıca, bağlantılı listeyi bir uygulama detayı olarak (çok verimsiz olduğu için) düşünmeyin, ancak nasıl yapılabileceğini kavramsallaştırmanın basit bir yolu olarak düşünün.
Kyle Cronin

10

İşte Closures'ın neden tekme attığına dair gerçek bir dünya örneği ... Bu benim Javascript kodumun dışında. Göstereyim.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

İşte nasıl kullanacağınız:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Şimdi, örneğin bu kod pasajı çalıştıktan 5 saniye sonra oynatmanın gecikmeli olarak başlamasını istediğinizi düşünün. Bu kolay delayve kapanması:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Ms delayile aradığınızda 5000, ilk snippet çalışır ve iletilen argümanları kapatılırken saklar. Daha sonra 5 saniye sonra, setTimeoutgeri arama gerçekleştiğinde, kapatma hala bu değişkenleri korur, böylece orijinal işlevi orijinal parametrelerle çağırabilir.
Bu bir tür körelme veya işlev dekorasyonudur.

Kapanışlar olmadan, bu değişkenlerin bir şekilde fonksiyonun dışında kalmasını sağlamanız gerekir, böylece fonksiyonun dışındaki kodu mantıksal olarak ona ait olan bir şeyle karıştırırsınız. Kapakların kullanılması, kodunuzun kalitesini ve okunabilirliğini büyük ölçüde artırabilir.


1
Küresel ad alanının bir parçası oldukları için dil veya ana bilgisayar nesnelerini genişletmenin genellikle kötü bir şey olarak kabul edildiğine dikkat edilmelidir
Jon Cooke

9

Serbest değişken içermeyen fonksiyonlara saf fonksiyonlar denir.

Bir veya daha fazla serbest değişken içeren fonksiyonlara kapatma adı verilir.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure


Bu neden eksileniyor? Aslında, serbest değişkenlere ve bağlı değişkenlere ve saf / kapalı fonksiyonlara ve saf / açık fonksiyonlara bu ayrım ile "doğru yolda", buradaki diğer clueless cevapların çoğundan daha fazladır: P (kapanışları fonksiyonlarla karıştırmak için indirim kapalı).
SasQ

Ben hiçbir gerçekten Fikir. Bu yüzden StackOverflow berbat. Sadece cevabımın kaynağına bakın. Bununla kim tartışabilir?
soundyogi

SO emmez ve "serbest değişken" terimini hiç duymadım
Kai

Serbest değişkenlerden bahsetmeden kapaklar hakkında konuşmak zor. Sadece bak. Standart CS terminolojisi.
ComDubh

"Bir veya daha fazla serbest değişken içeren işlevler kapanış olarak adlandırılır" doğru bir tanım değildir - kapanışlar her zaman birinci sınıf nesnelerdir.
ComDubh

7

tl; Dr.

Kapatma bir işlevdir ve kapsamı bir değişkene atanır (veya olarak kullanılır). Bu nedenle, isim kapatma: kapsam ve işlev tıpkı diğer herhangi bir varlık gibi kapatılır ve kullanılır.

Vikipedi tarzı derinlemesine açıklama

Wikipedia'ya göre, bir kapanış :

Birinci sınıf işlevlere sahip dillerde sözcüksel olarak kapsamlı ad bağlama uygulama teknikleri.

Bu ne anlama geliyor? Bazı tanımlara bakalım.

Bu örneği kullanarak kapanışları ve diğer ilgili tanımları açıklayacağım:

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

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

Birinci sınıf fonksiyonlar

Temel olarak bu, diğer herhangi bir varlık gibi fonksiyonları kullanabileceğimiz anlamına gelir . Bunları değiştirebilir, bağımsız değişken olarak aktarabilir, işlevlerden döndürebilir veya değişkenler için atayabiliriz. Teknik olarak, birinci sınıf vatandaşlar , dolayısıyla adı: birinci sınıf fonksiyonlar.

Yukarıdaki örnekte, startAtbir (döner anonim fonksiyonu atanmış olsun) işlevini closure1ve closure2. Gördüğünüz gibi JavaScript işlevleri diğer varlıklar (birinci sınıf vatandaşlar) gibi ele alır.

İsim bağlama

Ad bağlama , bir değişkenin (tanımlayıcı) hangi verilere başvurduğunu bulmakla ilgilidir . Burada kapsam gerçekten önemlidir, çünkü bir bağlamanın nasıl çözüleceğini belirleyecek olan şey budur.

Yukarıdaki örnekte:

  • İç anonim işlevin kapsamına ybağlıdır 3.
  • Gelen startAtbireyin kapsamı, xbağlı olduğu 1ya da 5(kapağın bağlı olarak).

Anonim işlevin kapsamı xiçinde herhangi bir değere bağlı değildir, bu nedenle üst ( startAt') kapsamda çözümlenmesi gerekir .

Sözcüksel kapsam belirleme

Wikipedia'nın dediği gibi kapsam:

Bağlamanın geçerli olduğu bir bilgisayar programının bölgesidir: ad, öğeye başvurmak için kullanılabilir .

İki teknik vardır:

  • Sözcüksel (statik) kapsam belirleme: Bir değişkenin tanımı, içerdiği blok veya işlev aranarak çözülür, ardından dıştaki blokta arama başarısız olursa, vb.
  • Dinamik kapsam belirleme: Arama fonksiyonu aranır, daha sonra o çağrı fonksiyonunu çağıran fonksiyon, vb. Çağrı yığınını ilerletir.

Daha fazla açıklama için bu soruyu kontrol ve Wikipedia bakabilirsiniz .

Yukarıdaki örnekte, JavaScript'in sözlüksel olarak kapsamlandığını görebiliriz, çünkü xçözümlendiğinde, startAtkaynak koduna (x'i arayan anonim işlev içeride tanımlanır startAt) dayalı olarak bağlamanın üst ( ') kapsamında aranır ve çağrı yığınına bağlı değil, işlevin çağrıldığı yol (kapsam).

Sarma (kapatma) yukarı

Örneğimizde, çağırdığımızda startAt, atanacak (birinci sınıf) bir işlev döndürecek closure1ve closure2böylece bir geçiş oluşturulacaktır, çünkü geçirilen değişkenler 1ve5 içinde kaydedilecektir startAtiade ile kapalı olacağını, s kapsamı' anonim işlev. Bu anonim işlevi aynı argüman ( ) aracılığıyla closure1ve closure2argümanıyla çağırdığımızda 3, değeri yhemen bulunur (bu işlevin parametresi olduğu için), ancak xanonim işlevin kapsamına bağlı değildir, bu nedenle çözünürlük devam eder (sözlüksel olarak) üst fonksiyon kapsamı (kapatmaya kaydedildi) xya 1da5. Şimdi özetleme için her şeyi biliyoruz, böylece sonuç iade edilebilir, daha sonra yazdırılabilir.

Şimdi, JavaScript'in temel bir parçası olan kapanışları ve nasıl davrandıklarını anlamalısınız.

tımar

Oh, ve ayrıca körelmenin ne hakkında olduğunu öğrendiniz : bir işlevi birden çok parametreli bir işlev kullanmak yerine bir işlemin her argümanını iletmek için işlevleri (kapanışları) kullanırsınız.


5

Kapatma , JavaScript'te bir işlevin kendi kapsam değişkenlerine, dış işlev değişkenlerine ve genel değişkenlere erişime sahip olduğu bir özelliktir.

Dış işlev geri döndükten sonra bile kapatma, dış işlev kapsamına erişebilir. Bu, kapatma işleminin, işlev bittikten sonra bile dış işlevinin değişkenlerini ve bağımsız değişkenlerini hatırlayabileceği ve erişebileceği anlamına gelir.

İç fonksiyon, kendi kapsamında tanımlanan değişkenlere, dış fonksiyonun kapsamına ve global kapsama erişebilir. Dış fonksiyon kendi kapsamında ve global kapsamda tanımlanan değişkene erişebilir.

Kapanış Örneği :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

Çıktı, iç fonksiyonun kendi değişkeni, dış fonksiyon değişkeni ve global değişken değerinin toplamı olan 20 olacaktır.


4

Normal bir durumda, değişkenler kapsam kuralı ile bağlanır: Yerel değişkenler sadece tanımlanan fonksiyon içinde çalışır. Kapanış, kolaylık sağlamak için bu kuralı geçici olarak ihlal etmenin bir yoludur.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

Yukarıdaki kodda, lambda(|n| a_thing * n}kapatmadır çünkü a_thinglambda (anonim bir işlev yaratıcısı) tarafından belirtilir.

Şimdi, ortaya çıkan anonim işlevi bir işlev değişkenine koyarsanız.

foo = n_times(4)

foo normal kapsam belirleme kuralını ihlal eder ve dahili olarak 4 kullanmaya başlar.

foo.call(3)

12 değerini döndürür.


2

Kısacası, fonksiyon işaretçisi sadece program kodu tabanındaki bir konuma (program sayacı gibi) bir işaretçi. Oysa Kapatma = İşlev işaretçisi + Yığın çerçevesi .

.


1

• Kapatma bir alt programdır ve tanımlandığı referans ortamdır

- Alt programın programdaki herhangi bir keyfi yerden çağrılabilmesi için referans ortamına ihtiyaç vardır

- İç içe yerleştirilmiş alt programlara izin vermeyen statik kapsamlı bir dilin kapatılması gerekmez

- Kapaklar yalnızca bir alt program iç içe geçme kapsamındaki değişkenlere erişebiliyorsa ve herhangi bir yerden çağrılabiliyorsa gereklidir

- Kapanışları desteklemek için bir uygulamanın bazı değişkenlere sınırsız ölçüde sağlaması gerekebilir (çünkü bir alt program normalde artık canlı olmayan yerel olmayan bir değişkene erişebilir)

Misal

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

İşte başka bir gerçek hayat örneği ve oyunlarda popüler olan bir betik dili kullanma - Lua. Stdin'in mevcut olmaması ile ilgili bir sorunu önlemek için bir kütüphane fonksiyonunun çalışma şeklini biraz değiştirmem gerekiyordu.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

Bu kod bloğu kapsamını tamamladığında (yerel olduğu için) old_dofile değeri kaybolur, ancak değer bir kapatma içine eklenir, bu nedenle yeni yeniden tanımlanmış dofile işlevi ona erişebilir veya işlevle birlikte bir 'upvalue'.


0

Gönderen Lua.org :

Bir işlev başka bir işlev içine alındığında, çevreleme işlevinden yerel değişkenlere tam erişime sahiptir; bu özelliğe sözcüksel kapsam belirleme denir. Bu açık gibi görünse de, öyle değil. Sözcüksel kapsam belirleme ve birinci sınıf işlevler programlama dilinde güçlü bir kavramdır, ancak çok az dil bu kavramı desteklemektedir.


0

Java dünyasındansanız, bir kapanışı bir sınıfın üye işleviyle karşılaştırabilirsiniz. Bu örneğe bak

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

İşlev gbir kapanıştır: gkapanır a. Yani gbir üye işleviyle akarşılaştırılabilir, bir sınıf alanıyla ve fbir sınıfla işlevle karşılaştırılabilir.


0

Kapanışlar Başka bir işlevin içinde tanımlanmış bir işleve sahip olduğumuzda, iç işlev dış işlevde bildirilen değişkenlere erişebilir. Kapaklar en iyi örneklerle açıklanmaktadır. Liste 2-18'de, iç işlevin dış kapsamdan bir değişkene (variableInOuterFunction) erişimi olduğunu görebilirsiniz. Dış işlevdeki değişkenler iç işlev tarafından kapatılmıştır (veya buna bağlanmıştır). Dolayısıyla kapanış terimi. Kavram kendi içinde yeterince basit ve oldukça sezgisel.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

kaynak: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf


0

Lütfen kapatmayı daha derinlemesine anlamak için kodun altına bir göz atın:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

İşte çıktı ne olacak? 0,1,2,3,4bu olmayacak5,5,5,5,5kapanış yüzünden

Peki nasıl çözecek? Cevap aşağıda:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Basitçe açıklayalım, bir fonksiyon yaratıldığında hiçbir şey olmayıncaya kadar 1 kodda 1 kez döngü için çağrıldı, ancak hemen çağrılmadı, yani 1 saniye sonra çağrıldığında ve bu da döngü tamamlanmadı ve mağaza değeri 5 için var i ve son olarak setTimeoutişlevi beş kez yürütmek ve yazdırmak5,5,5,5,5

Burada IIFE'yi kullanarak nasıl çözülür? Hemen Başlatma Fonksiyonu İfadesi

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Daha fazla bilgi için lütfen kapatmayı anlamak üzere yürütme bağlamını anlayın.

  • Bunu let (ES6 özelliği) kullanarak çözmek için bir çözüm daha var, ancak yukarıdaki başlık altında işlev çalışıyor

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Daha fazla açıklama:

Bellekte, döngü yürütmek için resim aşağıdaki gibi olun:

Döngü 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Döngü 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Döngü 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Döngü 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Döngü 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Burada i yürütülmez ve sonra tam döngü sonra, ben bellekte 5 depolanmış değer var ama kapsam çocuk işlevinde her zaman görünür böylece işlev setTimeoutbeş kez içten dışa yürütmek5,5,5,5,5

bu yüzden bu sorunu çözmek için yukarıda açıklandığı gibi IIFE kullanın.


Cevabınız için teşekkürler. kodu açıklamadan ayırırsanız daha okunabilir olur. (kod olmayan satırları girintilemeyin)
eMBee

0

Currying: Bir işlevi sadece argümanlarının bir alt kümesinden geçirerek kısmen değerlendirmenizi sağlar. Bunu düşün:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Kapatma: Kapatma, bir işlevin kapsamı dışındaki bir değişkene erişmekten başka bir şey değildir. Bir işlevin veya iç içe bir işlevin içindeki bir işlevin bir kapatma olmadığını hatırlamak önemlidir. İşlev kapsamı dışındaki değişkenlere erişmeniz gerektiğinde kapaklar her zaman kullanılır.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

0

Kapanış çok kolaydır. Bunu şu şekilde düşünebiliriz: Kapatma = işlev + sözcüksel ortamı

Aşağıdaki işlevi göz önünde bulundurun:

function init() {
    var name = “Mozilla”;
}

Yukarıdaki davada kapanış ne olacak? İşlev init () ve değişken ortamındaki değişkenler, yani ad. kapatma = init () + ad

Başka bir işlev düşünün:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

Buradaki kapanışlar ne olacak? İç fonksiyon dış fonksiyon değişkenlerine erişebilir. displayName (), üst işlevde (init ()) bildirilen değişken adına erişebilir. Ancak, displayName () öğesinde aynı yerel değişkenler varsa kullanılır.

Kapatma 1: init işlevi + (ad değişkeni + displayName () işlevi) -> sözcüksel kapsam

Kapatma 2: displayName işlevi + (ad değişkeni) -> sözcüksel kapsam


0

Kapanışlar JavaScript'i durumla sağlar.

Programlamadaki durum basitçe şeyleri hatırlamak demektir.

Misal

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

Yukarıdaki durumda, durum "a" değişkeninde saklanır. Bunu birkaç kez "a" ya 1 ekleyerek takip ediyoruz. Bunu sadece yapabiliriz çünkü değeri “hatırlayabiliriz”. Devlet sahibi "a" bu değeri bellekte tutar.

Genellikle, programlama dillerinde, şeyleri takip etmek, bilgileri hatırlamak ve daha sonra erişmek istersiniz.

Bu, diğer dillerde , genellikle sınıfların kullanımı ile gerçekleştirilir. Bir sınıf, tıpkı değişkenler gibi, durumunu takip eder. Ve bu sınıfın örnekleri de sırayla içinde devlete sahiptir. Durum, daha sonra depolayabileceğiniz ve alabileceğiniz bilgiler anlamına gelir.

Misal

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

"Oluşturma" yönteminden "ağırlığa" nasıl erişebiliriz? Devlet sayesinde. Bread sınıfının her bir örneği, hafızasında bu bilgiyi saklayabileceğimiz bir yer olan "durum" dan okuyarak kendi ağırlığını oluşturabilir.

Şimdi, JavaScript geçmişte sınıfları olmayan çok benzersiz bir dildir (şimdi var, ancak başlık altında sadece işlevler ve değişkenler var), bu nedenle Kapanışlar JavaScript'in bir şeyi hatırlaması ve daha sonra erişmesi için bir yol sağlar.

Misal

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

Yukarıdaki örnek bir değişkenle "durumu koruma" amacına ulaşmıştır. Bu harika! Ancak, bunun dezavantajı, değişkenin ("durum" sahibi) şimdi açıkta kalmasıdır. Daha iyisini yapabiliriz. Kapakları kullanabiliriz.

Misal

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

Bu fantastik.

Şimdi "sayım" fonksiyonumuz sayabilir. Sadece bunu yapabilir, çünkü durumu “tutabilir”. Bu durumda durum "n" değişkenidir. Bu değişken artık kapalı. Zaman ve mekanda kapalı. Zaman içinde onu kurtaramayacağınız, değiştiremeyeceğiniz, bir değer atayamayacağınız veya doğrudan onunla etkileşim kuramayacağınız için. Uzayda coğrafi olarak "countGenerator" işlevi içinde iç içe çünkü.

Bu neden harika? Çünkü başka karmaşık ve karmaşık bir araç (örn. Sınıflar, yöntemler, örnekler, vb.) İçermeden 1. uzaktan kontrol edebiliyoruz.

Devleti gizliyoruz, "n" değişkeni, onu özel değişken yapıyor! Ayrıca, bu değişkeni önceden tanımlanmış bir şekilde kontrol edebilen bir API oluşturduk. Özellikle, API'yı "count ()" gibi çağırabiliriz ve bu da "mesafe" den "n" ye 1 ekler. Hiçbir şekilde, herhangi bir şekilde "n" e erişemezsiniz.

JavaScript basitliği ile gerçekten şaşırtıcı.

Kapanışlar bunun nedeninin büyük bir parçası.


0

Groovy'de referansınız için basit bir örnek:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
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.