Seorang teman saya dan saya saat ini sedang mendiskusikan apa itu penutupan di JS dan apa yang tidak. Kami hanya ingin memastikan bahwa kami benar-benar memahaminya dengan benar.
Mari kita ambil contoh ini. Kami memiliki loop penghitungan dan ingin mencetak variabel penghitung pada konsol yang tertunda. Oleh karena itu kita gunakan setTimeout
dan penutupan untuk menangkap nilai variabel counter untuk memastikan bahwa tidak akan mencetak N kali nilai N.
Solusi yang salah tanpa penutupan atau apa pun yang dekat dengan penutupan adalah:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
yang tentu saja akan mencetak 10 kali nilai i
setelah loop, yaitu 10.
Jadi upayanya adalah:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
mencetak 0 hingga 9 seperti yang diharapkan.
Saya mengatakan kepadanya bahwa dia tidak menggunakan penutupan untuk menangkap i
, tetapi dia bersikeras bahwa dia. Saya membuktikan bahwa dia tidak menggunakan penutupan dengan meletakkan for loop body di dalam yang lain setTimeout
(meneruskan fungsi anonimnya setTimeout
), mencetak 10 kali 10 lagi. Hal yang sama berlaku jika saya menyimpan fungsinya dalam a var
dan menjalankannya setelah loop, juga mencetak 10 kali 10. Jadi argumen saya adalah bahwa dia tidak benar-benar menangkap nilaii
, membuat versinya bukan penutup.
Upaya saya adalah:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Jadi saya menangkap i
(dinamai i2
dalam penutupan), tapi sekarang saya mengembalikan fungsi lain dan menyebarkannya. Dalam kasus saya, fungsi yang diteruskan ke setTimeout benar-benar menangkap i
.
Sekarang siapa yang menggunakan penutupan dan siapa yang tidak?
Perhatikan bahwa kedua solusi mencetak 0 hingga 9 pada konsol tertunda, sehingga mereka memecahkan masalah asli, tetapi kami ingin memahami yang mana dari kedua solusi yang menggunakan penutupan untuk mencapai ini.
sumber
Jawaban:
Catatan Editor: Semua fungsi dalam JavaScript adalah penutup seperti yang dijelaskan dalam posting ini . Namun kami hanya tertarik untuk mengidentifikasi subset dari fungsi-fungsi ini yang menarik dari sudut pandang teoritis. Selanjutnya referensi ke kata closure akan merujuk pada subset fungsi ini kecuali dinyatakan lain.
Penjelasan sederhana untuk penutupan:
Sekarang mari kita gunakan ini untuk mencari tahu siapa yang menggunakan penutupan dan siapa yang tidak (demi penjelasan saya telah menyebutkan fungsi):
Kasus 1: Program Teman Anda
Dalam program di atas ada dua fungsi:
f
dang
. Mari kita lihat apakah itu adalah penutupan:Untuk
f
:i2
adalah variabel lokal .i
adalah variabel gratis .setTimeout
adalah variabel gratis .g
adalah variabel lokal .console
adalah variabel gratis .i
adalah terikat untuk lingkup global.setTimeout
adalah terikat untuk lingkup global.console
adalah terikat untuk lingkup global.i
tidak ditutup olehf
.setTimeout
tidak ditutup olehf
.console
tidak ditutup olehf
.Dengan demikian fungsinya
f
bukan merupakan penutupan.Untuk
g
:console
adalah variabel gratis .i2
adalah variabel gratis .console
adalah terikat untuk lingkup global.i2
adalah terikat dengan ruang lingkupf
.setTimeout
.console
tidak ditutup olehg
.i2
itu ditutup olehg
.Dengan demikian fungsi
g
adalah penutupan untuk variabel bebasi2
(yang merupakan upvalue untukg
) saat itu dirujuk dari dalamsetTimeout
.Buruk untuk Anda: Teman Anda menggunakan penutupan. Fungsi batin adalah penutup.
Kasus 2: Program Anda
Dalam program di atas ada dua fungsi:
f
dang
. Mari kita lihat apakah itu adalah penutupan:Untuk
f
:i2
adalah variabel lokal .g
adalah variabel lokal .console
adalah variabel gratis .console
adalah terikat untuk lingkup global.console
tidak ditutup olehf
.Demikian fungsinya
f
bukan merupakan penutupan.Untuk
g
:console
adalah variabel gratis .i2
adalah variabel gratis .console
adalah terikat untuk lingkup global.i2
adalah terikat dengan ruang lingkupf
.setTimeout
.console
tidak ditutup olehg
.i2
itu ditutup olehg
.Dengan demikian fungsi
g
adalah penutupan untuk variabel bebasi2
(yang merupakan upvalue untukg
) saat itu dirujuk dari dalamsetTimeout
.Bagus untukmu: Anda menggunakan penutupan. Fungsi batin adalah penutup.
Jadi Anda dan teman Anda menggunakan penutupan. Berhenti berdebat. Saya harap saya menjelaskan konsep penutupan dan bagaimana mengidentifikasi mereka untuk Anda berdua.
Edit: Penjelasan sederhana mengapa semua fungsi ditutup (kredit @Peter):
Pertama mari kita pertimbangkan program berikut (ini kontrolnya ):
lexicalScope
danregularFunction
bukan penutup dari definisi di atas .message
akan disiagakan karenaregularFunction
bukan merupakan penutupan (artinya ia memiliki akses ke semua variabel dalam cakupan induknya - termasukmessage
).message
memang ada peringatan.Selanjutnya mari kita pertimbangkan program berikut (ini alternatifnya ):
closureFunction
merupakan penutup dari definisi di atas .message
tidak akan diberitahu karenaclosureFunction
merupakan penutupan (yaitu hanya memiliki akses ke semua variabel non-lokal pada saat fungsi dibuat ( lihat jawaban ini ) - ini tidak termasukmessage
).message
sebenarnya sedang disiagakan.Apa yang kita simpulkan dari ini?
sumber
g
berjalan dalam cakupansetTimeout
, tetapi dalam Kasus 2 Anda mengatakan ituf
berjalan dalam cakupan global. Keduanya ada dalam setTimeout, jadi apa bedanya?Menurut
closure
definisi:Anda menggunakan
closure
jika Anda mendefinisikan suatu fungsi yang menggunakan variabel yang didefinisikan di luar fungsi. (kami menyebut variabel variabel bebas ).Mereka semua menggunakan
closure
(bahkan dalam contoh 1).sumber
i2
yang didefinisikan di luar.Singkatnya Javascript Closures memungkinkan suatu fungsi untuk mengakses variabel yang dideklarasikan dalam fungsi lexical-parent .
Mari kita lihat penjelasan yang lebih rinci. Untuk memahami penutupan, penting untuk memahami bagaimana JavaScript membatasi variabel.
Lingkup
Dalam JavaScript, lingkup didefinisikan dengan fungsi. Setiap fungsi menentukan ruang lingkup baru.
Perhatikan contoh berikut;
memanggil f print
Sekarang mari kita perhatikan kasus kita memiliki fungsi yang
g
didefinisikan dalam fungsi lainf
.Kami akan memanggil
f
para orang tua leksikal darig
. Seperti yang dijelaskan sebelumnya kita sekarang memiliki 2 cakupan; ruang lingkupf
dan ruang lingkupg
.Tetapi satu ruang lingkup "di dalam" ruang lingkup yang lain, jadi apakah ruang lingkup fungsi anak bagian dari ruang lingkup fungsi orang tua? Apa yang terjadi dengan variabel yang dideklarasikan dalam ruang lingkup fungsi induk; Apakah saya dapat mengaksesnya dari ruang lingkup fungsi anak? Di situlah penutupan terjadi.
Penutupan
Dalam JavaScript fungsi
g
tidak hanya dapat mengakses variabel apa pun yang dideklarasikan dalam ruang lingkupg
tetapi juga mengakses variabel yang dideklarasikan dalam ruang lingkup fungsi indukf
.Pertimbangkan untuk mengikuti;
memanggil f print
Mari kita lihat garisnya
console.log(foo);
. Pada titik ini kami berada dalam cakupang
dan kami mencoba mengakses variabelfoo
yang dideklarasikan dalam lingkupf
. Tetapi seperti yang dinyatakan sebelumnya kita dapat mengakses variabel apa pun yang dideklarasikan dalam fungsi induk leksikal yang merupakan kasus di sini;g
adalah induk leksikal darif
. Karenahello
itu dicetak.Sekarang mari kita lihat garis
console.log(bar);
. Pada titik ini kami berada dalam cakupanf
dan kami mencoba mengakses variabelbar
yang dideklarasikan dalam lingkupg
.bar
tidak dideklarasikan dalam ruang lingkup saat ini dan fungsinyag
bukan induk darif
, oleh karena itubar
itu tidak terdefinisiSebenarnya kita juga dapat mengakses variabel yang dideklarasikan dalam ruang lingkup fungsi "grand parent" leksikal. Karena itu jika ada fungsi yang
h
didefinisikan dalam fungsi tersebutg
maka
h
akan dapat mengakses semua variabel yang dideklarasikan dalam lingkup fungsih
,g
danf
. Ini dilakukan dengan penutupan . Dalam penutupan JavaScript memungkinkan kita untuk mengakses variabel apa pun yang dideklarasikan dalam fungsi induk leksikal, dalam fungsi grand parent leksikal, dalam fungsi grand-grand parent leksikal, dll. Ini dapat dilihat sebagai rantai lingkup ;scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
sampai fungsi induk terakhir yang tidak memiliki induk leksikal.Objek jendela
Sebenarnya rantai tidak berhenti pada fungsi induk terakhir. Ada satu ruang lingkup khusus lagi; yang lingkup global . Setiap variabel yang tidak dideklarasikan dalam suatu fungsi dianggap dideklarasikan dalam lingkup global. Ruang lingkup global memiliki dua spesialisasi;
window
objek.Oleh karena itu ada dua cara untuk mendeklarasikan variabel
foo
dalam lingkup global; baik dengan tidak mendeklarasikannya dalam suatu fungsi atau dengan mengatur propertifoo
objek window.Kedua upaya menggunakan penutupan
Sekarang setelah Anda membaca penjelasan yang lebih terperinci, kini mungkin tampak jelas bahwa kedua solusi menggunakan penutupan. Tapi yang pasti, mari kita buktikan.
Mari kita membuat Bahasa Pemrograman baru; JavaScript-Tanpa-Penutupan. Seperti namanya, JavaScript-No-Closure identik dengan JavaScript kecuali tidak mendukung Closures.
Dengan kata lain;
Baiklah, mari kita lihat apa yang terjadi dengan solusi pertama dengan JavaScript-No-Closure;
karena itu ini akan mencetak
undefined
10 kali dalam JavaScript-No-Closure.Oleh karena itu solusi pertama menggunakan penutupan.
Mari kita lihat solusi kedua;
karena itu ini akan mencetak
undefined
10 kali dalam JavaScript-No-Closure.Kedua solusi menggunakan penutupan.
Sunting: Diasumsikan bahwa 3 snippet kode ini tidak didefinisikan dalam lingkup global. Kalau tidak, variabel
foo
dani
akan mengikat kewindow
objek dan karena itu dapat diakses melaluiwindow
objek dalam JavaScript dan JavaScript-No-Closuresumber
i
tidak terdefinisi? Anda cukup merujuk ke lingkup orang tua, yang masih berlaku jika tidak ada penutupan.i2
untuki
pada saat Anda mendefinisikan fungsi Anda. Ini membuati
BUKAN variabel bebas. Namun, kami menganggap fungsi Anda sebagai penutupan, tetapi tanpa variabel bebas, itulah intinya.Saya tidak pernah senang dengan cara orang menjelaskan ini.
Kunci untuk memahami penutupan adalah untuk memahami akan seperti apa JS tanpa penutupan.
Tanpa penutupan, ini akan menimbulkan kesalahan
Setelah outerFunc kembali dalam versi JavaScript yang dinonaktifkan karena penutupan imajiner, referensi ke outerVar akan menjadi sampah yang dikumpulkan dan tidak meninggalkan apa pun di sana untuk fungsi internal sebagai referensi.
Penutupan pada dasarnya adalah aturan khusus yang mendukung dan memungkinkan vars tersebut ada saat fungsi dalam merujuk variabel fungsi luar. Dengan penutupan vars yang dirujuk dipertahankan bahkan setelah fungsi luar dilakukan atau 'ditutup' jika itu membantu Anda mengingat titik.
Bahkan dengan penutupan, siklus hidup vars lokal dalam suatu fungsi tanpa fungsi dalam yang mereferensikan penduduk lokalnya bekerja sama seperti dalam versi tanpa penutupan. Ketika fungsi selesai, penduduk setempat mengumpulkan sampah.
Setelah Anda memiliki referensi dalam fungsi internal ke var luar, namun itu seperti kusen pintu akan menghalangi pengumpulan sampah untuk vars yang direferensikan.
Cara yang mungkin lebih akurat untuk melihat penutupan, adalah bahwa fungsi bagian dalam pada dasarnya menggunakan lingkup dalam sebagai foudnation lingkupnya sendiri.
Tetapi konteks yang dirujuk sebenarnya, gigih, tidak seperti snapshot. Berulang kali menembakkan fungsi dalam yang dikembalikan yang terus bertambah dan mencatat var lokal fungsi luar akan terus mengingatkan nilai yang lebih tinggi.
sumber
Anda berdua menggunakan penutupan.
Saya akan menggunakan definisi Wikipedia di sini:
Upaya teman Anda dengan jelas menggunakan variabel
i
, yang bukan lokal, dengan mengambil nilainya dan membuat salinan untuk disimpan ke lokali2
.Upaya Anda sendiri diteruskan
i
(yang di situs panggilan berada dalam ruang lingkup) ke fungsi anonim sebagai argumen. Ini bukan penutupan sejauh ini, tapi kemudian fungsi itu mengembalikan fungsi lain yang referensi samai2
. Karena di dalam fungsi anonim dalami2
bukan lokal, ini membuat penutupan.sumber
i
kei2
, kemudian mendefinisikan beberapa logika dan mengeksekusi fungsi ini. Jika saya tidak segera menjalankannya , tetapi menyimpannya dalam var, dan mengeksekusi setelah loop, itu akan mencetak 10, bukan? Jadi tidak menangkap saya.i
baik. Perilaku yang Anda gambarkan bukanlah hasil dari penutupan vs non-penutupan; itu adalah hasil dari variabel tertutup yang diubah sementara itu. Anda melakukan hal yang sama menggunakan sintaks yang berbeda dengan segera memanggil fungsi dan meneruskani
sebagai argumen (yang menyalin nilainya saat ini di tempat). Jika Anda memasukkannya kesetTimeout
dalam yang lainsetTimeout
, hal yang sama akan terjadi.Anda dan teman Anda berdua menggunakan penutupan:
Dalam fungsi kode teman Anda
function(){ console.log(i2); }
didefinisikan dalam penutupan fungsi anonimfunction(){ var i2 = i; ...
dan dapat membaca / menulis variabel lokali2
.Dalam fungsi kode Anda
function(){ console.log(i2); }
didefinisikan di dalam penutupan fungsifunction(i2){ return ...
dan dapat membaca / menulis berharga lokali2
(dinyatakan dalam kasus ini sebagai parameter).Dalam kedua kasus fungsi
function(){ console.log(i2); }
kemudian diteruskan kesetTimeout
.Setara lainnya (tetapi dengan pemanfaatan memori lebih sedikit) adalah:
sumber
Penutupan
Penutupan bukan fungsi, dan bukan ekspresi. Ini harus dilihat sebagai semacam 'snapshot' dari variabel yang digunakan di luar functionscope dan digunakan di dalam function. Secara tata bahasa, kita harus mengatakan: 'ambil penutup variabel'.
Sekali lagi, dengan kata lain: Penutupan adalah salinan dari konteks variabel yang relevan di mana fungsi bergantung.
Sekali lagi (naif): Penutupan memiliki akses ke variabel yang tidak dilewatkan sebagai parameter.
Ingatlah bahwa konsep-konsep fungsional ini sangat tergantung pada bahasa pemrograman / lingkungan yang Anda gunakan. Dalam JavaScript, penutupan tergantung pada pelingkupan leksikal (yang berlaku di sebagian besar bahasa-c).
Jadi, mengembalikan fungsi sebagian besar mengembalikan fungsi anonim / tanpa nama. Ketika variabel akses fungsi, tidak dilewatkan sebagai parameter, dan dalam lingkup (leksikal) nya, penutupan telah diambil.
Jadi, tentang contoh Anda:
Semua menggunakan penutupan. Jangan bingung titik eksekusi dengan penutupan. Jika 'snapshot' dari penutupan diambil pada saat yang salah, nilainya mungkin tidak terduga tetapi tentu saja penutupan diambil!
sumber
Mari kita lihat dua cara:
Deklarasikan dan segera jalankan fungsi anonim yang berjalan
setTimeout()
dalam konteksnya sendiri. Nilai saat ini darii
diawetkan dengan membuat salinan menjadi yangi2
pertama; ini bekerja karena eksekusi segera.Menyatakan konteks eksekusi untuk fungsi dalam di mana nilai saat
i
ini dipertahankan ke dalami2
; pendekatan ini juga menggunakan eksekusi segera untuk mempertahankan nilai.Penting
Harus disebutkan bahwa run semantik TIDAK sama antara kedua pendekatan; fungsi batin Anda diteruskan ke
setTimeout()
mana fungsi batinnya memanggilsetTimeout()
dirinya sendiri.Membungkus kedua kode di dalam kode lain
setTimeout()
tidak membuktikan bahwa hanya pendekatan kedua yang menggunakan penutupan, tidak ada hal yang sama untuk memulai.Kesimpulan
Kedua metode menggunakan penutupan, sehingga turun ke selera pribadi; pendekatan kedua lebih mudah untuk "bergerak" atau menyamaratakan.
sumber
setTimeout()
?i
dapat diubah tanpa mempengaruhi apa fungsi yang harus dicetak, tidak tergantung di mana atau kapan kita menjalankannya.()
, sehingga melewati fungsi, dan Anda melihat 10 kali output10
.()
inilah yang membuat kodenya berfungsi, seperti halnya Anda(i)
; Anda tidak hanya membungkus kodenya, Anda membuat perubahan terhadapnya .. karena itu Anda tidak dapat membuat perbandingan yang valid lagi.Saya menulis ini beberapa waktu lalu untuk mengingatkan diri saya tentang apa penutupan itu dan bagaimana cara kerjanya di JS.
Penutupan adalah fungsi yang, ketika dipanggil, menggunakan ruang lingkup di mana ia dinyatakan, bukan ruang lingkup di mana ia dipanggil. Dalam javaScript, semua fungsi berperilaku seperti ini. Nilai variabel dalam ruang lingkup tetap ada selama ada fungsi yang masih menunjuk padanya. Pengecualian untuk aturan adalah 'ini', yang merujuk ke objek yang fungsinya ada di dalam ketika dipanggil.
sumber
Setelah memeriksa dengan cermat, sepertinya Anda berdua menggunakan penutupan.
Dalam kasus teman Anda,
i
diakses di dalam fungsi anonim 1 dani2
diakses di fungsi anonim 2 tempatconsole.log
ada.Dalam kasus Anda, Anda mengakses
i2
di dalam fungsi anonim di manaconsole.log
ada. Tambahkandebugger;
pernyataan sebelumconsole.log
dan di alat pengembang krom di bawah "Variabel lingkup" itu akan memberi tahu apa ruang lingkup variabel.sumber
Pertimbangkan yang berikut ini. Ini membuat dan menciptakan kembali fungsi
f
yang ditutupi
, tetapi berbeda !:sementara yang berikut ditutup pada "fungsi" itu sendiri "
(sendiri! potongan setelah ini menggunakan satu referensi
f
)atau lebih eksplisit:
NB. definisi terakhir
f
adalahfunction(){ console.log(9) }
sebelumnya0
dicetak.Peringatan! Konsep penutupan dapat menjadi gangguan paksaan dari esensi pemrograman dasar:
x-refs .:
Bagaimana cara kerja penutupan JavaScript?
Penjelasan Penutupan Javascript
Apakah Penutupan (JS) Membutuhkan Fungsi Di Dalam Fungsi
Bagaimana memahami penutupan dalam Javascript?
Javascript kebingungan variabel lokal dan global
sumber
Run' only was desired - not sure how to remove the
Salin`Saya ingin membagikan contoh dan penjelasan tentang penutupan. Saya membuat contoh python, dan dua angka untuk menunjukkan negara stack.
Output dari kode ini adalah sebagai berikut:
Berikut adalah dua gambar untuk menunjukkan tumpukan dan penutupan yang melekat pada objek fungsi.
ketika fungsi dikembalikan dari pembuat
saat fungsi dipanggil nanti
Ketika fungsi dipanggil melalui parameter atau variabel nonlokal, kode membutuhkan binding variabel lokal seperti margin_top, padding serta a, b, n. Untuk memastikan kode fungsi berfungsi, bingkai tumpukan fungsi pembuat yang sudah lama hilang harus dapat diakses, yang didukung dalam penutupan yang dapat kita temukan bersama dengan objek pesan fungsi.
sumber
delete
tautan di bawah jawabannya.