Saya mengajukan pertanyaan tentang Kari dan penutupan disebutkan. Apa itu penutupan? Bagaimana hubungannya dengan kari?
Saya mengajukan pertanyaan tentang Kari dan penutupan disebutkan. Apa itu penutupan? Bagaimana hubungannya dengan kari?
Jawaban:
Saat Anda mendeklarasikan variabel lokal, variabel itu memiliki cakupan. Secara umum, variabel lokal hanya ada di dalam blok atau fungsi tempat Anda mendeklarasikannya.
function() {
var a = 1;
console.log(a); // works
}
console.log(a); // fails
Jika saya mencoba mengakses variabel lokal, sebagian besar bahasa akan mencarinya dalam cakupan saat ini, kemudian naik melalui cakupan induk hingga mencapai lingkup root.
var a = 1;
function() {
console.log(a); // works
}
console.log(a); // works
Ketika suatu blok atau fungsi selesai, variabel lokalnya tidak lagi diperlukan dan biasanya dihembuskan dari memori.
Beginilah biasanya kita mengharapkan sesuatu bekerja.
Penutupan adalah ruang lingkup persisten yang berpegang pada variabel lokal bahkan setelah eksekusi kode telah pindah dari blok itu. Bahasa yang mendukung penutupan (seperti JavaScript, Swift, dan Ruby) akan memungkinkan Anda untuk menyimpan referensi ke ruang lingkup (termasuk cakupan induknya), bahkan setelah blok di mana variabel-variabel tersebut dinyatakan telah selesai dieksekusi, asalkan Anda menyimpan referensi ke blok itu atau berfungsi di suatu tempat.
Objek lingkup dan semua variabel lokalnya terikat dengan fungsi dan akan bertahan selama fungsi itu tetap ada.
Ini memberi kita portabilitas fungsi. Kita dapat mengharapkan variabel apa pun yang berada dalam ruang lingkup ketika fungsi pertama kali didefinisikan masih dalam ruang lingkup ketika kita memanggil fungsi, bahkan jika kita memanggil fungsi dalam konteks yang sama sekali berbeda.
Berikut adalah contoh yang sangat sederhana dalam JavaScript yang menggambarkan intinya:
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();
Di sini saya telah mendefinisikan suatu fungsi di dalam suatu fungsi. Fungsi dalam memperoleh akses ke semua variabel lokal fungsi luar, termasuk a
. Variabel a
dalam ruang lingkup untuk fungsi dalam.
Biasanya ketika suatu fungsi keluar, semua variabel lokalnya terpesona. Namun, jika kita mengembalikan fungsi bagian dalam dan menetapkannya ke variabel fnc
sehingga tetap ada setelah outer
keluar, semua variabel yang berada dalam ruang lingkup saat inner
didefinisikan juga tetap ada . Variabel a
telah ditutup - itu dalam penutupan.
Perhatikan bahwa variabel a
tersebut benar-benar pribadi untuk fnc
. Ini adalah cara membuat variabel pribadi dalam bahasa pemrograman fungsional seperti JavaScript.
Seperti yang mungkin bisa Anda tebak, ketika saya menyebutnya fnc()
mencetak nilai a
, yaitu "1".
Dalam bahasa tanpa penutupan, variabel a
akan menjadi sampah yang dikumpulkan dan dibuang ketika fungsi outer
keluar. Memanggil fnc akan membuat kesalahan karena a
tidak ada lagi.
Dalam JavaScript, variabel a
tetap ada karena ruang lingkup variabel dibuat ketika fungsi pertama kali dideklarasikan dan bertahan selama fungsi terus ada.
a
termasuk dalam ruang lingkup outer
. Ruang lingkup inner
memiliki pointer orangtua ke lingkup outer
. fnc
adalah variabel yang menunjuk ke inner
. a
tetap ada selama masih fnc
ada. a
berada dalam penutupan.
Saya akan memberikan contoh (dalam JavaScript):
function makeCounter () {
var count = 0;
return function () {
count += 1;
return count;
}
}
var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...
Apa fungsi ini, makeCounter, lakukan adalah mengembalikan fungsi, yang kami sebut x, yang akan dihitung satu setiap kali dipanggil. Karena kami tidak memberikan parameter apa pun kepada x, entah bagaimana ia harus mengingat hitungan. Ia tahu di mana menemukannya berdasarkan apa yang disebut pelingkupan leksikal - ia harus melihat ke tempat di mana ia didefinisikan untuk menemukan nilai. Nilai "tersembunyi" ini adalah apa yang disebut penutupan.
Ini adalah contoh kari saya lagi:
function add (a) {
return function (b) {
return a + b;
}
}
var add3 = add(3);
add3(4); returns 7
Apa yang dapat Anda lihat adalah bahwa ketika Anda memanggil add dengan parameter a (yang adalah 3), nilai itu terkandung dalam penutupan fungsi yang dikembalikan yang kami definisikan sebagai add3. Dengan begitu, ketika kita memanggil add3 ia tahu di mana menemukan nilai untuk melakukan penambahan.
Jawaban Kyle cukup bagus. Saya pikir satu-satunya klarifikasi tambahan adalah bahwa penutupan pada dasarnya adalah snapshot dari tumpukan pada titik fungsi lambda dibuat. Kemudian ketika fungsi dijalankan kembali tumpukan dikembalikan ke keadaan itu sebelum menjalankan fungsi. Jadi seperti Kyle menyebutkan, bahwa nilai tersembunyi ( count
) tersedia ketika fungsi lambda dijalankan.
Pertama-tama, bertentangan dengan apa yang dikatakan sebagian besar orang di sini, penutupan bukanlah suatu fungsi ! Jadi apa yang itu?
Ini adalah seperangkat simbol yang didefinisikan dalam "konteks sekitarnya" fungsi (dikenal sebagai lingkungannya ) yang membuatnya menjadi ekspresi TERTUTUP (yaitu, ekspresi di mana setiap simbol didefinisikan dan memiliki nilai, sehingga dapat dievaluasi).
Misalnya, ketika Anda memiliki fungsi JavaScript:
function closed(x) {
return x + 3;
}
itu adalah ekspresi tertutup karena semua simbol yang ada di dalamnya didefinisikan di dalamnya (artinya jelas), sehingga Anda dapat mengevaluasinya. Dengan kata lain, itu mandiri .
Tetapi jika Anda memiliki fungsi seperti ini:
function open(x) {
return x*y + 3;
}
itu adalah ekspresi terbuka karena ada simbol di dalamnya yang belum didefinisikan di dalamnya. Yaitu y
,. Ketika melihat fungsi ini, kita tidak bisa mengatakan apa y
itu dan apa artinya, kita tidak tahu nilainya, jadi kita tidak bisa mengevaluasi ungkapan ini. Yaitu kita tidak dapat memanggil fungsi ini sampai kita tahu apa y
yang dimaksudkan di dalamnya. Ini y
disebut variabel bebas .
Ini y
memohon definisi, tetapi definisi ini bukan bagian dari fungsi - itu didefinisikan di tempat lain, dalam "konteks sekitarnya" (juga dikenal sebagai lingkungan ). Setidaknya itulah yang kami harapkan: P
Sebagai contoh, ini dapat didefinisikan secara global:
var y = 7;
function open(x) {
return x*y + 3;
}
Atau bisa didefinisikan dalam fungsi yang membungkusnya:
var global = 2;
function wrapper(y) {
var w = "unused";
return function(x) {
return x*y + 3;
}
}
Bagian dari lingkungan yang memberikan variabel bebas dalam ekspresi artinya, adalah penutup . Ini disebut cara ini, karena mengubah ekspresi terbuka menjadi ekspresi tertutup , dengan menyediakan definisi yang hilang ini untuk semua variabel bebasnya , sehingga kami dapat mengevaluasinya.
Dalam contoh di atas, fungsi bagian dalam (yang kami tidak memberikan nama karena kami tidak membutuhkannya) adalah ekspresi terbuka karena variabel y
di dalamnya bebas - definisinya berada di luar fungsi, dalam fungsi yang membungkusnya . The lingkungan untuk itu fungsi anonim adalah himpunan variabel:
{
global: 2,
w: "unused",
y: [whatever has been passed to that wrapper function as its parameter `y`]
}
Sekarang, penutupan adalah bagian dari lingkungan ini yang menutup fungsi bagian dalam dengan menyediakan definisi untuk semua variabel bebasnya . Dalam kasus kami, satu-satunya variabel bebas dalam fungsi dalam adalah y
, jadi penutupan fungsi itu adalah bagian dari lingkungannya:
{
y: [whatever has been passed to that wrapper function as its parameter `y`]
}
Dua simbol lain yang didefinisikan dalam lingkungan bukan bagian dari penutupan fungsi itu, karena itu tidak mengharuskan mereka untuk menjalankan. Mereka tidak perlu menutupnya .
Lebih lanjut tentang teori di balik itu di sini: https://stackoverflow.com/a/36878651/434562
Perlu dicatat bahwa dalam contoh di atas, fungsi wrapper mengembalikan fungsi bagian dalamnya sebagai nilai. Saat kita memanggil fungsi ini dapat menjadi jauh dalam waktu dari saat fungsi telah didefinisikan (atau dibuat). Secara khusus, fungsi pembungkusnya tidak lagi berjalan, dan parameternya yang sudah ada di tumpukan panggilan sudah tidak ada lagi: P Ini membuat masalah, karena fungsi bagian dalam y
harus ada ketika dipanggil! Dengan kata lain, itu membutuhkan variabel dari penutupannya untuk entah bagaimana hidup lebih lama dari fungsi pembungkus dan berada di sana ketika dibutuhkan. Oleh karena itu, fungsi bagian dalam harus membuat snapshot dari variabel-variabel ini yang membuat penutupannya dan menyimpannya di tempat yang aman untuk digunakan nanti. (Di suatu tempat di luar tumpukan panggilan.)
Dan inilah mengapa orang sering mengacaukan istilah closure sebagai jenis fungsi khusus yang dapat melakukan snapshot dari variabel eksternal yang mereka gunakan, atau struktur data yang digunakan untuk menyimpan variabel-variabel ini untuk nanti. Tapi saya harap Anda mengerti sekarang bahwa itu bukan penutupan itu sendiri - itu hanya cara untuk menerapkan penutupan dalam bahasa pemrograman, atau mekanisme bahasa yang memungkinkan variabel dari penutupan fungsi berada di sana saat dibutuhkan. Ada banyak kesalahpahaman di sekitar penutupan yang (tidak perlu) membuat subjek ini jauh lebih membingungkan dan rumit.
Penutupan adalah fungsi yang bisa merujuk keadaan di fungsi lain. Misalnya, dalam Python, ini menggunakan penutupan "bagian dalam":
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
Untuk membantu memfasilitasi pemahaman tentang penutupan, mungkin berguna untuk memeriksa bagaimana mereka diimplementasikan dalam bahasa prosedural. Penjelasan ini akan mengikuti implementasi penutupan yang sederhana dalam Skema.
Untuk memulai, saya harus memperkenalkan konsep namespace. Saat Anda memasukkan perintah ke dalam juru bahasa Skema, itu harus mengevaluasi berbagai simbol dalam ekspresi dan mendapatkan nilainya. Contoh:
(define x 3)
(define y 4)
(+ x y) returns 7
Ekspresi define menyimpan nilai 3 di tempat untuk x dan nilai 4 di tempat untuk y. Kemudian ketika kita memanggil (+ xy), interpreter mencari nilai-nilai di namespace dan dapat melakukan operasi dan mengembalikan 7.
Namun, dalam Skema ada ekspresi yang memungkinkan Anda untuk sementara menimpa nilai simbol. Ini sebuah contoh:
(define x 3)
(define y 4)
(let ((x 5))
(+ x y)) returns 9
x returns 3
Apa yang dilakukan kata kunci let adalah memperkenalkan namespace baru dengan x sebagai nilai 5. Anda akan melihat bahwa itu masih dapat melihat bahwa y adalah 4, sehingga jumlah yang dikembalikan menjadi 9. Anda juga dapat melihat bahwa setelah ekspresi telah berakhir x kembali menjadi 3. Dalam pengertian ini, x telah ditutup sementara oleh nilai lokal.
Bahasa prosedural dan berorientasi objek memiliki konsep yang sama. Setiap kali Anda mendeklarasikan variabel dalam suatu fungsi yang memiliki nama yang sama dengan variabel global Anda mendapatkan efek yang sama.
Bagaimana kita menerapkan ini? Cara sederhana adalah dengan daftar tertaut - kepala berisi nilai baru dan ekornya berisi namespace lama. Ketika Anda perlu mencari simbol, Anda mulai dari kepala dan turun ke bagian bawah.
Sekarang mari kita lompat ke implementasi fungsi kelas satu untuk saat ini. Lebih atau kurang, suatu fungsi adalah seperangkat instruksi untuk dieksekusi ketika fungsi disebut memuncak pada nilai kembali. Ketika kita membaca suatu fungsi, kita dapat menyimpan instruksi ini di belakang layar dan menjalankannya ketika fungsi tersebut dipanggil.
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns ?
Kami mendefinisikan x menjadi 3 dan plus-x sebagai parameternya, y, ditambah nilai x. Akhirnya kita memanggil plus-x di lingkungan di mana x telah ditutup oleh x baru, yang ini bernilai 5. Jika kita hanya menyimpan operasi, (+ xy), untuk fungsi plus-x, karena kita berada dalam konteks dari x menjadi 5 hasil yang dikembalikan adalah 9. Inilah yang disebut pelingkupan dinamis.
Namun, Skema, Common Lisp, dan banyak bahasa lain memiliki apa yang disebut pelingkupan leksikal - selain menyimpan operasi (+ xy) kami juga menyimpan namespace pada titik tertentu. Dengan begitu, ketika kita mencari nilai-nilai kita dapat melihat bahwa x, dalam konteks ini, benar-benar 3. Ini adalah penutup.
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns 7
Singkatnya, kita bisa menggunakan daftar tertaut untuk menyimpan keadaan namespace pada saat definisi fungsi, memungkinkan kita untuk mengakses variabel dari lingkup melampirkan, serta memberikan kita kemampuan untuk secara lokal menutupi variabel tanpa mempengaruhi sisa dari 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?
Berikut ini adalah contoh nyata dari dunia mengapa Closures menendang pantat ... Ini langsung dari kode Javascript saya. Biarkan saya ilustrasikan.
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);
};
Dan inilah cara Anda menggunakannya:
var startPlayback = function(track) {
Player.play(track);
};
startPlayback(someTrack);
Sekarang bayangkan Anda ingin pemutaran mulai ditunda, seperti misalnya 5 detik kemudian setelah potongan kode ini berjalan. Yah itu mudah dengan delay
dan itu penutupan:
startPlayback.delay(5000, someTrack);
// Keep going, do other things
Saat Anda menelepon delay
dengan 5000
ms, cuplikan pertama berjalan, dan menyimpan argumen yang diteruskan di penutupannya. Kemudian 5 detik kemudian, ketika setTimeout
panggilan balik terjadi, penutupan masih mempertahankan variabel-variabel tersebut, sehingga dapat memanggil fungsi asli dengan parameter asli.
Ini adalah jenis kari, atau fungsi dekorasi.
Tanpa penutup, Anda harus mempertahankan status variabel tersebut di luar fungsi, sehingga membuang kode di luar fungsi dengan sesuatu yang secara logis berada di dalamnya. Menggunakan penutupan dapat sangat meningkatkan kualitas dan keterbacaan kode Anda.
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
}
Penutupan adalah fungsi dan ruang lingkupnya ditetapkan untuk (atau digunakan sebagai) variabel. Dengan demikian, penutupan nama: ruang lingkup dan fungsi tertutup dan digunakan sama seperti entitas lainnya.
Menurut Wikipedia, penutupan adalah:
Teknik untuk menerapkan pengikatan nama leksikal dalam bahasa dengan fungsi kelas satu.
Apa artinya? Mari kita melihat beberapa definisi.
Saya akan menjelaskan penutupan dan definisi terkait lainnya dengan menggunakan contoh ini:
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)
Pada dasarnya itu berarti kita dapat menggunakan fungsi seperti entitas lainnya . Kita dapat memodifikasinya, meneruskannya sebagai argumen, mengembalikannya dari fungsi atau menetapkannya untuk variabel. Secara teknis, mereka adalah warga negara kelas satu , karenanya namanya: fungsi kelas satu.
Dalam contoh di atas, startAt
mengembalikan fungsi ( anonim ) yang fungsinya ditugaskan untuk closure1
dan closure2
. Jadi seperti yang Anda lihat JavaScript memperlakukan fungsi sama seperti entitas lainnya (warga negara kelas satu).
Pengikatan nama adalah tentang mencari tahu data apa yang menjadi referensi variabel (pengidentifikasi) . Lingkup ini sangat penting di sini, karena hal itulah yang akan menentukan bagaimana suatu ikatan diselesaikan.
Dalam contoh di atas:
y
terikat 3
.startAt
ruang lingkup, x
terikat ke 1
atau 5
(tergantung pada penutupan).Di dalam ruang lingkup fungsi anonim, x
tidak terikat dengan nilai apa pun, jadi itu perlu diselesaikan dalam startAt
ruang lingkup atas.
Seperti yang dikatakan Wikipedia , ruang lingkup:
Merupakan wilayah program komputer tempat pengikatan valid: di mana nama dapat digunakan untuk merujuk ke entitas .
Ada dua teknik:
Untuk penjelasan lebih lanjut, lihat pertanyaan ini dan lihat di Wikipedia .
Pada contoh di atas, kita dapat melihat bahwa JavaScript dibatasi secara leksikal, karena ketika x
diselesaikan, pengikatan dicari dalam startAt
lingkup atas, berdasarkan pada kode sumber (fungsi anonim yang mencari x didefinisikan di dalam startAt
) dan tidak berdasarkan tumpukan panggilan, cara (ruang lingkup di mana) fungsi dipanggil.
Dalam contoh kita, ketika kita memanggil startAt
, itu akan mengembalikan fungsi (kelas satu) yang akan ditugaskan closure1
dan closure2
dengan demikian penutupan dibuat, karena variabel yang dikirimkan 1
dan 5
akan disimpan dalam startAt
ruang lingkup, yang akan ditutup dengan yang dikembalikan fungsi anonim. Ketika kita memanggil fungsi anonim ini melalui closure1
dan closure2
dengan argumen yang sama ( 3
), nilai y
akan segera ditemukan (karena itu adalah parameter dari fungsi itu), tetapi x
tidak terikat dalam ruang lingkup fungsi anonim, sehingga resolusi berlanjut di (fungsi leksikal) lingkup fungsi atas (yang disimpan dalam penutupan) di mana x
ditemukan terikat ke salah satu 1
atau5
. Sekarang kita tahu segalanya untuk penjumlahan sehingga hasilnya dapat dikembalikan, lalu dicetak.
Sekarang Anda harus memahami penutupan dan bagaimana mereka berperilaku, yang merupakan bagian mendasar dari JavaScript.
Oh, dan Anda juga mempelajari apa itu currying : Anda menggunakan fungsi (penutup) untuk melewati setiap argumen operasi bukannya menggunakan satu fungsi dengan beberapa parameter.
Penutupan adalah fitur dalam JavaScript di mana fungsi memiliki akses ke variabel lingkupnya sendiri, akses ke variabel fungsi luar dan akses ke variabel global.
Penutupan memiliki akses ke lingkup fungsi luarnya bahkan setelah fungsi luar kembali. Ini berarti penutupan dapat mengingat dan mengakses variabel dan argumen dari fungsi luarnya bahkan setelah fungsi selesai.
Fungsi dalam dapat mengakses variabel yang didefinisikan dalam ruang lingkupnya sendiri, ruang lingkup fungsi luar, dan ruang lingkup global. Dan fungsi luar dapat mengakses variabel yang ditentukan dalam lingkupnya sendiri dan lingkup global.
Contoh Penutupan :
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();
Output akan menjadi 20 yang jumlah variabel fungsi dalamnya sendiri, variabel fungsi luar dan nilai variabel global.
Dalam situasi normal, variabel terikat oleh aturan pelingkupan: Variabel lokal hanya berfungsi dalam fungsi yang ditentukan. Penutupan adalah cara melanggar aturan ini sementara untuk kenyamanan.
def n_times(a_thing)
return lambda{|n| a_thing * n}
end
dalam kode di atas, lambda(|n| a_thing * n}
adalah penutupan karena a_thing
disebut oleh lambda (pembuat fungsi anonim).
Sekarang, jika Anda meletakkan fungsi anonim yang dihasilkan dalam variabel fungsi.
foo = n_times(4)
foo akan melanggar aturan pelingkupan normal dan mulai menggunakan 4 secara internal.
foo.call(3)
mengembalikan 12.
• Penutupan adalah subprogram dan lingkungan referensi tempat definisi itu ditentukan
- Lingkungan referensi diperlukan jika subprogram dapat dipanggil dari sembarang tempat dalam program
- Bahasa dengan cakupan statis yang tidak mengizinkan subprogram bersarang tidak perlu ditutup
- Penutupan hanya diperlukan jika subprogram dapat mengakses variabel dalam cakupan bersarang dan dapat dipanggil dari mana saja
- Untuk mendukung penutupan, implementasi mungkin perlu memberikan batas tidak terbatas pada beberapa variabel (karena subprogram dapat mengakses variabel nonlokal yang biasanya tidak lagi hidup)
Contoh
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 />″);
Berikut adalah contoh kehidupan nyata yang lain, dan menggunakan bahasa scripting yang populer di permainan - Lua. Saya perlu sedikit mengubah cara fungsi perpustakaan bekerja untuk menghindari masalah dengan stdin tidak tersedia.
local old_dofile = dofile
function dofile( filename )
if filename == nil then
error( 'Can not use default of stdin.' )
end
old_dofile( filename )
end
Nilai old_dofile menghilang ketika blok kode ini menyelesaikan ruang lingkupnya (karena bersifat lokal), namun nilainya telah ditutup dalam penutupan, sehingga fungsi dofile baru yang didefinisikan ulang DAPAT mengaksesnya, atau lebih tepatnya salinan yang disimpan bersama dengan fungsi sebagai 'upvalue'.
Dari Lua.org :
Ketika suatu fungsi ditulis terlampir dalam fungsi lain, ia memiliki akses penuh ke variabel lokal dari fungsi terlampir; fitur ini disebut pelingkupan leksikal. Meskipun itu mungkin terdengar jelas, tidak. Pelingkupan leksikal, ditambah fungsi kelas satu, adalah konsep yang kuat dalam bahasa pemrograman, tetapi beberapa bahasa mendukung konsep itu.
Jika Anda berasal dari dunia Java, Anda dapat membandingkan penutupan dengan fungsi anggota suatu kelas. Lihatlah contoh ini
var f=function(){
var a=7;
var g=function(){
return a;
}
return g;
}
Fungsi g
adalah penutupan: g
menutup a
di Jadi. g
Dapat dibandingkan dengan fungsi anggota, a
dapat dibandingkan dengan bidang kelas, dan fungsi f
dengan kelas.
Penutupan Setiap kali kita memiliki fungsi yang didefinisikan di dalam fungsi lain, fungsi dalam memiliki akses ke variabel yang dinyatakan dalam fungsi luar. Penutupan paling baik dijelaskan dengan contoh. Dalam Listing 2-18, Anda bisa melihat bahwa fungsi dalam memiliki akses ke variabel (variableInOuterFunction) dari lingkup luar. Variabel dalam fungsi luar telah ditutup oleh (atau terikat) fungsi dalam. Karena itu istilah penutupan. Konsep itu sendiri cukup sederhana dan cukup intuitif.
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!
sumber: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf
Silakan lihat kode di bawah ini untuk memahami penutupan lebih dalam:
for(var i=0; i< 5; i++){
setTimeout(function(){
console.log(i);
}, 1000);
}
Di sini apa yang akan menjadi output? 0,1,2,3,4
bukan 5,5,5,5,5
karena penutupan
Jadi bagaimana itu akan menyelesaikannya? Jawabannya di bawah:
for(var i=0; i< 5; i++){
(function(j){ //using IIFE
setTimeout(function(){
console.log(j);
},1000);
})(i);
}
Biar saya jelaskan, ketika suatu fungsi dibuat tidak ada yang terjadi sampai ia dipanggil untuk loop dalam kode 1 disebut 5 kali tetapi tidak dipanggil segera jadi ketika dipanggil yaitu setelah 1 detik dan juga ini tidak sinkron jadi sebelum ini untuk loop selesai dan menyimpan nilai 5 di var i dan akhirnya jalankan setTimeout
fungsi lima kali dan cetak5,5,5,5,5
Di sini cara mengatasi menggunakan IIFE yaitu Ekspresi Fungsi Immediate Invoking
(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
Untuk lebih lanjut, harap pahami konteks eksekusi untuk memahami penutupan.
Ada satu solusi lagi untuk menyelesaikan ini menggunakan let (fitur ES6) tetapi di bawah kap fungsi di atas bekerja
for(let i=0; i< 5; i++){
setTimeout(function(){
console.log(i);
},1000);
}
Output: 0,1,2,3,4
=> Penjelasan lebih lanjut:
Di memori, ketika untuk loop jalankan gambar make seperti di bawah ini:
Loop 1)
setTimeout(function(){
console.log(i);
},1000);
Loop 2)
setTimeout(function(){
console.log(i);
},1000);
Loop 3)
setTimeout(function(){
console.log(i);
},1000);
Loop 4)
setTimeout(function(){
console.log(i);
},1000);
Loop 5)
setTimeout(function(){
console.log(i);
},1000);
Di sini saya tidak dieksekusi dan kemudian setelah loop lengkap, var saya menyimpan nilai 5 dalam memori tetapi ruang lingkupnya selalu terlihat dalam fungsi anak-anak sehingga ketika fungsi dijalankan dalam setTimeout
lima kali ia dicetak5,5,5,5,5
jadi untuk mengatasi ini gunakan IIFE seperti yang dijelaskan di atas.
Currying: Ini memungkinkan Anda untuk mengevaluasi sebagian fungsi dengan hanya mengirimkan sebagian dari argumennya. Pertimbangkan ini:
function multiply (x, y) {
return x * y;
}
const double = multiply.bind(null, 2);
const eight = double(4);
eight == 8;
Penutupan: Penutupan tidak lebih dari mengakses variabel di luar lingkup fungsi. Penting untuk diingat bahwa fungsi di dalam suatu fungsi atau fungsi bersarang bukanlah penutupan. Penutupan selalu digunakan ketika perlu mengakses variabel di luar lingkup fungsi.
function apple(x){
function google(y,z) {
console.log(x*y);
}
google(7,2);
}
apple(3);
// the answer here will be 21
Penutupan sangat mudah. Kita dapat mempertimbangkannya sebagai berikut: Penutupan = fungsi + lingkungan leksikalnya
Pertimbangkan fungsi berikut:
function init() {
var name = “Mozilla”;
}
Apa yang akan menjadi penutupan dalam kasus di atas? Function init () dan variabel dalam lingkungan leksikalnya yaitu nama. Penutupan = init () + nama
Pertimbangkan fungsi lain:
function init() {
var name = “Mozilla”;
function displayName(){
alert(name);
}
displayName();
}
Apa yang akan menjadi penutupan di sini? Fungsi dalam dapat mengakses variabel fungsi luar. displayName () dapat mengakses nama variabel yang dinyatakan dalam fungsi induk, init (). Namun, variabel lokal yang sama di displayName () akan digunakan jika ada.
Penutupan 1: fungsi init + (variabel nama + fungsi displayName ()) -> ruang lingkup leksikal
Penutupan 2: fungsi displayName + (variabel nama) -> ruang lingkup leksikal
Keadaan dalam pemrograman berarti mengingat sesuatu.
Contoh
var a = 0;
a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3
Dalam kasus di atas, status disimpan dalam variabel "a". Kami mengikuti dengan menambahkan 1 ke "a" beberapa kali. Kita hanya dapat melakukan itu karena kita dapat "mengingat" nilainya. Pemegang status, "a", menyimpan nilai itu dalam memori.
Seringkali, dalam bahasa pemrograman, Anda ingin melacak hal-hal, mengingat informasi dan mengaksesnya di lain waktu.
Ini, dalam bahasa lain , umumnya dicapai melalui penggunaan kelas. Kelas, seperti halnya variabel, melacak kondisinya. Dan contoh-contoh dari kelas itu, pada gilirannya, juga memiliki keadaan di dalamnya. Negara hanya berarti informasi yang dapat Anda simpan dan ambil nanti.
Contoh
class Bread {
constructor (weight) {
this.weight = weight;
}
render () {
return `My weight is ${this.weight}!`;
}
}
Bagaimana kita dapat mengakses "bobot" dari dalam metode "render"? Yah, terima kasih sudah menyatakan. Setiap instance dari class Bread dapat memberikan bobotnya sendiri dengan membacanya dari "state", tempat di memori di mana kita dapat menyimpan informasi itu.
Sekarang, JavaScript adalah bahasa yang sangat unik yang yang secara historis tidak memiliki kelas (sekarang, tetapi di bawah tenda hanya ada fungsi dan variabel) sehingga Closures menyediakan cara bagi JavaScript untuk mengingat sesuatu dan mengaksesnya nanti.
Contoh
var n = 0;
var count = function () {
n = n + 1;
return n;
};
count(); // # 1
count(); // # 2
count(); // # 3
Contoh di atas mencapai tujuan "menjaga keadaan" dengan variabel. Ini bagus! Namun, ini memiliki kelemahan bahwa variabel (pemegang "negara") sekarang terbuka. Kita bisa melakukan yang lebih baik. Kita bisa menggunakan Penutupan.
Contoh
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
Sekarang fungsi "menghitung" kami dapat menghitung. Ia hanya dapat melakukannya karena ia dapat "menahan" keadaan. Keadaan dalam hal ini adalah variabel "n". Variabel ini sekarang ditutup. Tertutup dalam ruang dan waktu. Pada waktunya karena Anda tidak akan pernah dapat memulihkannya, ubah, tetapkan nilai, atau berinteraksi langsung dengannya. Di ruang angkasa karena secara geografis bersarang di dalam fungsi "countGenerator".
Kenapa ini fantastis? Karena tanpa melibatkan alat canggih dan rumit lainnya (misalnya kelas, metode, instance, dll) kita dapat 1. menyembunyikan 2. mengontrol dari kejauhan
Kami menyembunyikan negara, variabel "n", yang membuatnya menjadi variabel pribadi! Kami juga telah membuat API yang dapat mengontrol variabel ini dengan cara yang telah ditentukan sebelumnya. Secara khusus, kita dapat memanggil API seperti "count ()" dan itu menambahkan 1 ke "n" dari "jarak". Dengan cara apa pun, bentuk atau bentuk siapa pun tidak akan dapat mengakses "n" kecuali melalui API.
Penutupan adalah bagian besar mengapa ini terjadi.