Apa itu 'Penutupan'?

432

Saya mengajukan pertanyaan tentang Kari dan penutupan disebutkan. Apa itu penutupan? Bagaimana hubungannya dengan kari?

Ben
sumber
22
Sekarang apa sebenarnya penutupannya ??? Beberapa jawaban mengatakan, penutupan adalah fungsinya. Ada yang bilang itu tumpukan. Beberapa jawaban mengatakan, itu adalah nilai "tersembunyi". Untuk pemahaman saya, itu adalah fungsi + variabel terlampir.
Roland
3
Menjelaskan apa penutupan itu: stackoverflow.com/questions/4103750/…
dietbuddha
Lihat juga Apa itu penutupan? di softwareengineering.stackexchange
B12Toaster
Menjelaskan apa itu penutupan dan kasus penggunaan umum: trungk18.com/experience/javascript-closure
Sasuke91

Jawaban:

744

Lingkup variabel

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 variabel lokal yang persisten

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.

Sebagai contoh

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 adalam 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 fncsehingga tetap ada setelah outerkeluar, semua variabel yang berada dalam ruang lingkup saat innerdidefinisikan juga tetap ada . Variabel atelah ditutup - itu dalam penutupan.

Perhatikan bahwa variabel atersebut 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 aakan menjadi sampah yang dikumpulkan dan dibuang ketika fungsi outerkeluar. Memanggil fnc akan membuat kesalahan karena atidak ada lagi.

Dalam JavaScript, variabel atetap ada karena ruang lingkup variabel dibuat ketika fungsi pertama kali dideklarasikan dan bertahan selama fungsi terus ada.

atermasuk dalam ruang lingkup outer. Ruang lingkup innermemiliki pointer orangtua ke lingkup outer. fncadalah variabel yang menunjuk ke inner. atetap ada selama masih fncada. aberada dalam penutupan.

superluminary
sumber
116
Saya pikir ini adalah contoh yang cukup bagus dan mudah dipahami.
user12345613
16
Terima kasih atas penjelasannya yang luar biasa, saya telah melihat banyak tetapi ini saatnya saya benar-benar mendapatkannya.
Dimitar Dimitrov
2
Bisakah saya memiliki contoh bagaimana ini bekerja di perpustakaan seperti JQuery seperti yang dinyatakan dalam paragraf ke 2 hingga terakhir? Saya tidak sepenuhnya mengerti itu.
DPM
6
Hai Jubbat, ya, buka jquery.js dan lihat baris pertama. Anda akan melihat fungsi dibuka. Sekarang lewati sampai akhir, Anda akan melihat window.jQuery = window. $ = JQuery. Kemudian fungsi ditutup dan dieksekusi sendiri. Anda sekarang memiliki akses ke $ function, yang pada gilirannya memiliki akses ke fungsi-fungsi lain yang didefinisikan dalam penutupan. Apakah itu menjawab pertanyaan Anda?
superluminary
4
Penjelasan terbaik di web. Jauh lebih sederhana dari yang saya kira
Mantis
95

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.

Kyle Cronin
sumber
4
IDK, bahasa apa (mungkin F #) yang Anda gunakan dalam bahasa di atas. Bisakah tolong berikan contoh di atas dalam pseudocode? Saya kesulitan memahami ini.
pengguna
1
@crucifiedsoul Ini Skema. ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/intro.txt
Kyle Cronin
3
@KyleCronin Contoh yang bagus, terima kasih. T: Apakah lebih tepat mengatakan "nilai tersembunyi disebut sebagai penutupan", atau "fungsi yang menyembunyikan nilai adalah penutupan"? Atau "proses menyembunyikan nilai adalah penutupan"? Terima kasih!
2
@ RobertTume Pertanyaan bagus. Secara semantik, istilah "penutupan" agak ambigu. Definisi pribadi saya adalah bahwa kombinasi nilai tersembunyi dan penggunaan fungsi melampirkannya merupakan penutupan.
Kyle Cronin
1
@KyleCronin Terima kasih - Saya memiliki skema jangka menengah pada hari Senin. :) Ingin memiliki konsep "penutupan" yang solid di kepala saya. Terima kasih telah mengirim jawaban yang bagus untuk pertanyaan OP!
58

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.

Ben Childs
sumber
14
Bukan hanya stack - melainkan lingkup leksikal terlampir yang dipertahankan, terlepas dari apakah mereka disimpan di stack atau heap (atau keduanya).
Matt Fenwick
38

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 yitu 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 yyang dimaksudkan di dalamnya. Ini ydisebut variabel bebas .

Ini ymemohon 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 ydi 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 yharus 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.

SasQ
sumber
1
Sebuah analogi yang mungkin membantu pemula untuk ini adalah penutupan mengikat semua ujung longgar , yang adalah apa yang dilakukan seseorang ketika mereka mencari penutupan (atau menyelesaikan semua referensi yang diperlukan, atau ...). Ya, itu membantu saya untuk berpikir seperti itu: o)
Will Crawford
Saya telah membaca banyak definisi penutupan selama bertahun-tahun, tetapi saya pikir ini adalah favorit saya sejauh ini. Saya kira kita semua memiliki cara kita sendiri dalam memetakan konsep mental seperti ini dan ini sangat cocok dengan milik saya.
Jason S.
29

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
John Millikin
sumber
23

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.

Kyle Cronin
sumber
oke, terima kasih atas jawaban Anda, saya pikir akhirnya saya punya ide tentang penutupan. Tetapi ada satu pertanyaan besar: "kita bisa menggunakan daftar tertaut untuk menyimpan keadaan namespace pada saat definisi fungsi, memungkinkan kita untuk mengakses variabel yang jika tidak tidak lagi berada dalam ruang lingkup." 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: Maaf, kalimat itu tidak masuk akal, jadi saya memperbaruinya. Saya harap ini lebih masuk akal sekarang. Juga, jangan menganggap daftar tertaut sebagai detail implementasi (karena sangat tidak efisien) tetapi sebagai cara sederhana untuk membuat konsep bagaimana hal itu bisa dilakukan.
Kyle Cronin
10

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 delaydan itu penutupan:

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

Saat Anda menelepon delaydengan 5000ms, cuplikan pertama berjalan, dan menyimpan argumen yang diteruskan di penutupannya. Kemudian 5 detik kemudian, ketika setTimeoutpanggilan 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.

adamJLev
sumber
1
Perlu dicatat bahwa memperluas bahasa atau objek host umumnya dianggap sebagai hal yang buruk karena mereka adalah bagian dari namespace global
Jon Cooke
9

Fungsi yang tidak mengandung variabel bebas disebut fungsi murni.

Fungsi yang mengandung satu atau lebih variabel bebas disebut closure.

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

soundyogi
sumber
Mengapa ini diminimalkan? Ini sebenarnya jauh lebih "di jalur yang benar" dengan perbedaan itu menjadi variabel bebas dan variabel terikat, dan fungsi murni / tertutup dan fungsi tidak murni / terbuka, daripada sebagian besar jawaban tidak mengerti lainnya di sini: P (diskon untuk penutupan yang membingungkan dengan fungsi sedang ditutup).
SasQ
Saya tidak punya Ide, sungguh. Inilah sebabnya mengapa StackOverflow menyebalkan. Lihat saja sumber Jawaban saya. Siapa yang bisa berdebat dengan itu?
soundyogi
SO tidak payah dan saya tidak pernah mendengar istilah "variabel bebas"
Kai
Sulit untuk berbicara tentang penutupan tanpa menyebutkan variabel gratis. Lihat saja. Terminologi CS standar.
ComDubh
"Fungsi yang mengandung satu atau lebih variabel bebas disebut penutupan" bukan definisi yang benar - penutupan selalu merupakan objek kelas satu.
ComDubh
7

tl; dr

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.

Penjelasan gaya Wikipedia yang mendalam

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)

Fungsi kelas satu

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, startAtmengembalikan fungsi ( anonim ) yang fungsinya ditugaskan untuk closure1dan closure2. Jadi seperti yang Anda lihat JavaScript memperlakukan fungsi sama seperti entitas lainnya (warga negara kelas satu).

Mengikat nama

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:

  • Dalam lingkup fungsi anonim dalam, yterikat 3.
  • Dalam startAtruang lingkup, xterikat ke 1atau 5(tergantung pada penutupan).

Di dalam ruang lingkup fungsi anonim, xtidak terikat dengan nilai apa pun, jadi itu perlu diselesaikan dalam startAtruang lingkup atas.

Pelingkupan leksikal

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:

  • Pelingkupan leksikal (statis): Definisi variabel diselesaikan dengan mencari blok atau fungsinya, kemudian jika itu gagal mencari blok yang berisi luar, dan seterusnya.
  • Pelingkupan dinamis: Fungsi panggilan dicari, kemudian fungsi yang memanggil fungsi panggilan, dan seterusnya, meningkatkan tumpukan panggilan.

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 xdiselesaikan, pengikatan dicari dalam startAtlingkup 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.

Membungkus (menutup)

Dalam contoh kita, ketika kita memanggil startAt, itu akan mengembalikan fungsi (kelas satu) yang akan ditugaskan closure1dan closure2dengan demikian penutupan dibuat, karena variabel yang dikirimkan 1dan 5akan disimpan dalam startAtruang lingkup, yang akan ditutup dengan yang dikembalikan fungsi anonim. Ketika kita memanggil fungsi anonim ini melalui closure1dan closure2dengan argumen yang sama ( 3), nilai yakan segera ditemukan (karena itu adalah parameter dari fungsi itu), tetapi xtidak terikat dalam ruang lingkup fungsi anonim, sehingga resolusi berlanjut di (fungsi leksikal) lingkup fungsi atas (yang disimpan dalam penutupan) di mana xditemukan terikat ke salah satu 1atau5. 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.

Kari

Oh, dan Anda juga mempelajari apa itu currying : Anda menggunakan fungsi (penutup) untuk melewati setiap argumen operasi bukannya menggunakan satu fungsi dengan beberapa parameter.

totymedli
sumber
5

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.

rahul sharma
sumber
4

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_thingdisebut 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.

Eugene Yokota
sumber
2

Singkatnya, pointer fungsi hanyalah sebuah pointer ke lokasi di basis kode program (seperti penghitung program). Sedangkan Closure = Function pointer + Stack frame .

.

RoboAlex
sumber
1

• 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 />″);
BoraKurucu
sumber
0

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'.

Nigel Atkinson
sumber
0

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.

pengguna5731811
sumber
0

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 gadalah penutupan: gmenutup adi Jadi. gDapat dibandingkan dengan fungsi anggota, adapat dibandingkan dengan bidang kelas, dan fungsi fdengan kelas.

ericj
sumber
0

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

shohan
sumber
0

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,4bukan 5,5,5,5,5karena 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 setTimeoutfungsi 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 setTimeoutlima kali ia dicetak5,5,5,5,5

jadi untuk mengatasi ini gunakan IIFE seperti yang dijelaskan di atas.

arun
sumber
Terima kasih atas jawaban anda. akan lebih mudah dibaca jika Anda memisahkan kode dari penjelasan. (jangan lekukan baris yang bukan kode)
eMBee
0

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
pengguna11335201
sumber
0

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

Rumel
sumber
0

Penutupan memberi status pada JavaScript.

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

Ini fantastis.

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.

JavaScript benar-benar luar biasa dalam kesederhanaannya.

Penutupan adalah bagian besar mengapa ini terjadi.

Claudio
sumber
0

Contoh sederhana di Groovy untuk referensi Anda:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
GraceMeng
sumber