Currying hakkında bir soru sordum ve kapaklardan bahsedildi. Kapanış nedir? Körelme ile ilişkisi nedir?
Currying hakkında bir soru sordum ve kapaklardan bahsedildi. Kapanış nedir? Körelme ile ilişkisi nedir?
Yanıtlar:
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, 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.
İş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 a
iç 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 fnc
sonra da devam etmesi için bir değişkene outer
atarsak, tanımlandığı zaman kapsamdaki tüm değişkenler inner
de devam eder . Değişken a
kapatıldı - bir kapanış içinde.
Değişkenin a
tamamen ö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 a
işlevden outer
çıkıldığında çöp toplanır ve atılır . Fnc çağrıldığında a
artı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.
a
kapsamına girer outer
. Kapsamının kapsamına ilişkin inner
bir üst işaretçi vardır outer
. fnc
işaret eden bir değişkendir inner
. a
devam ettiği sürece fnc
devam eder. a
kapanış içinde.
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.
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.
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 y
olduğunu ve ne anlama geldiğini söyleyemeyiz , değerini bilmiyoruz, bu yüzden bu ifadeyi değerlendiremeyiz. Yani içinde ne y
demek istediğini söyleyene kadar bu işlevi çağıramayız . Buna serbest değişkeny
denir .
Bu y
bir 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ü y
bunun 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.
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
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ı.
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?
İş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 delay
ve kapanması:
startPlayback.delay(5000, someTrack);
// Keep going, do other things
Ms delay
ile aradığınızda 5000
, ilk snippet çalışır ve iletilen argümanları kapatılırken saklar. Daha sonra 5 saniye sonra, setTimeout
geri 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.
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
}
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.
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)
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, startAt
bir (döner anonim fonksiyonu atanmış olsun) işlevini closure1
ve closure2
. Gördüğünüz gibi JavaScript işlevleri diğer varlıklar (birinci sınıf vatandaşlar) gibi ele alır.
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:
y
bağlıdır 3
.startAt
bireyin kapsamı, x
bağlı olduğu 1
ya da 5
(kapağın bağlı olarak).Anonim işlevin kapsamı x
içinde herhangi bir değere bağlı değildir, bu nedenle üst ( startAt
') kapsamda çözümlenmesi gerekir .
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:
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, startAt
kaynak 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).
Örneğimizde, çağırdığımızda startAt
, atanacak (birinci sınıf) bir işlev döndürecek closure1
ve closure2
böylece bir geçiş oluşturulacaktır, çünkü geçirilen değişkenler 1
ve5
içinde kaydedilecektir startAt
iade ile kapalı olacağını, s kapsamı' anonim işlev. Bu anonim işlevi aynı argüman ( ) aracılığıyla closure1
ve closure2
argümanıyla çağırdığımızda 3
, değeri y
hemen bulunur (bu işlevin parametresi olduğu için), ancak x
anonim 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) x
ya 1
da5
. Ş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.
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.
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.
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_thing
lambda (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.
• 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 />″);
İş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'.
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.
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 g
bir kapanıştır: g
kapanır a
. Yani g
bir üye işleviyle a
karşılaştırılabilir, bir sınıf alanıyla ve f
bir sınıfla işlevle karşılaştırılabilir.
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
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,4
bu olmayacak5,5,5,5,5
kapanış 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 setTimeout
iş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 setTimeout
beş 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.
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
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
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
Ş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.
Kapanışlar bunun nedeninin büyük bir parçası.