Sesekali saya melihat "penutupan" disebutkan, dan saya mencoba mencarinya tetapi Wiki tidak memberikan penjelasan yang saya mengerti. Bisakah seseorang membantu saya di sini?
language-features
closures
Gablin
sumber
sumber
Jawaban:
(Penafian: ini adalah penjelasan dasar; sejauh definisi berjalan, saya menyederhanakan sedikit)
Cara paling sederhana untuk memikirkan penutupan adalah fungsi yang dapat disimpan sebagai variabel (disebut sebagai "fungsi kelas-pertama"), yang memiliki kemampuan khusus untuk mengakses variabel-variabel lain secara lokal dengan cakupan tempat ia dibuat.
Contoh (JavaScript):
Fungsi 1 ditugaskan untuk
document.onclick
dandisplayValOfBlack
merupakan penutupan. Anda dapat melihat bahwa keduanya merujuk variabel booleanblack
, tetapi variabel tersebut ditugaskan di luar fungsi. Karenablack
bersifat lokal pada ruang lingkup di mana fungsi didefinisikan , pointer ke variabel ini dipertahankan.Jika Anda meletakkan ini di halaman HTML:
Ini menunjukkan bahwa keduanya memiliki akses yang sama
black
, dan dapat digunakan untuk menyimpan keadaan tanpa objek pembungkus.Panggilan ke
setKeyPress
adalah untuk menunjukkan bagaimana suatu fungsi dapat dilewati seperti variabel apa pun. The lingkup diawetkan dalam penutupan tersebut masih satu di mana fungsi didefinisikan.Penutupan biasanya digunakan sebagai pengendali acara, terutama dalam JavaScript dan ActionScript. Penggunaan penutupan yang baik akan membantu Anda secara implisit mengikat variabel ke penangan acara tanpa harus membuat pembungkus objek. Namun, penggunaan yang ceroboh akan menyebabkan kebocoran memori (seperti ketika event handler yang tidak digunakan tetapi diawetkan adalah satu-satunya hal untuk mempertahankan benda besar dalam memori, terutama objek DOM, mencegah pengumpulan sampah).
1: Sebenarnya, semua fungsi dalam JavaScript adalah penutup.
sumber
black
dideklarasikan di dalam suatu fungsi, bukankah itu akan hancur ketika tumpukan dibuka ...?black
dideklarasikan di dalam suatu fungsi, bukankah itu akan dihancurkan". Ingat juga bahwa jika Anda mendeklarasikan objek dalam suatu fungsi dan kemudian menetapkannya ke variabel yang hidup di tempat lain, objek itu dipertahankan karena ada referensi lain untuk itu.Penutupan pada dasarnya hanya cara berbeda dalam memandang suatu objek. Objek adalah data yang memiliki satu atau lebih fungsi yang terikat padanya. Penutupan adalah fungsi yang memiliki satu atau lebih variabel terikat padanya. Keduanya pada dasarnya identik, setidaknya pada tingkat implementasi. Perbedaan sebenarnya adalah dari mana mereka berasal.
Dalam pemrograman berorientasi objek, Anda mendeklarasikan kelas objek dengan mendefinisikan variabel anggotanya dan metodenya (fungsi anggota) di muka, dan kemudian Anda membuat instance kelas itu. Setiap instance dilengkapi dengan salinan data anggota, diinisialisasi oleh konstruktor. Anda kemudian memiliki variabel tipe objek, dan menyebarkannya sebagai bagian dari data, karena fokusnya adalah pada sifatnya sebagai data.
Dalam penutupan, di sisi lain, objek tidak didefinisikan di muka seperti kelas objek, atau dipakai melalui panggilan konstruktor dalam kode Anda. Sebagai gantinya, Anda menulis penutupan sebagai fungsi di dalam fungsi lain. Penutupan dapat merujuk ke salah satu variabel lokal fungsi luar, dan kompiler mendeteksi itu dan memindahkan variabel-variabel ini dari ruang stack fungsi luar ke deklarasi objek tersembunyi penutupan. Anda kemudian memiliki variabel tipe penutupan, dan meskipun pada dasarnya objek di bawah tenda, Anda menyebarkannya sebagai referensi fungsi, karena fokusnya adalah pada sifatnya sebagai fungsi.
sumber
Istilah closure berasal dari fakta bahwa sepotong kode (blok, fungsi) dapat memiliki variabel bebas yang ditutup (yaitu terikat pada nilai) oleh lingkungan di mana blok kode didefinisikan.
Ambil contoh definisi fungsi Scala:
Dalam tubuh fungsi ada dua nama (variabel)
v
dank
menunjukkan dua nilai integer. Namav
diikat karena dideklarasikan sebagai argumen fungsiaddConstant
(dengan melihat deklarasi fungsi kita tahu bahwav
akan diberi nilai ketika fungsi dipanggil). Namanyak
adalah free wrt fungsiaddConstant
karena fungsi tidak mengandung petunjuk tentang apa nilaik
terikat (dan bagaimana).Untuk mengevaluasi panggilan seperti:
kita harus menetapkan
k
nilai, yang hanya dapat terjadi jika namak
tersebut didefinisikan dalam konteks yangaddConstant
didefinisikan. Sebagai contoh:Sekarang kita telah mendefinisikan
addConstant
dalam konteks di manak
didefinisikan,addConstant
telah menjadi penutup karena semua variabel bebasnya sekarang ditutup (terikat pada nilai):addConstant
dapat dipanggil dan diedarkan seolah-olah itu adalah fungsi. Perhatikan variabel bebask
terikat ke nilai ketika penutupan didefinisikan , sedangkan variabel argumenv
terikat ketika penutupan dipanggil .Jadi penutupan pada dasarnya adalah fungsi atau blok kode yang dapat mengakses nilai-nilai non-lokal melalui variabel bebasnya setelah ini terikat oleh konteks.
Dalam banyak bahasa, jika Anda menggunakan penutupan hanya sekali Anda bisa membuatnya anonim , misalnya
Perhatikan bahwa fungsi tanpa variabel bebas adalah kasus khusus dari penutupan (dengan set variabel bebas yang kosong). Secara analog, fungsi anonim adalah kasus khusus dari penutupan anonim , yaitu fungsi anonim adalah penutupan anonim tanpa variabel bebas.
sumber
Penjelasan sederhana dalam JavaScript:
alert(closure)
akan menggunakan nilai yang dibuat sebelumnya dariclosure
.alertValue
Namespace fungsi yang dikembalikan akan terhubung ke namespace di manaclosure
variabel berada. Ketika Anda menghapus seluruh fungsi, nilaiclosure
variabel akan dihapus, tetapi sampai saat itu,alertValue
fungsi akan selalu dapat membaca / menulis nilai variabelclosure
.Jika Anda menjalankan kode ini, iterasi pertama akan memberikan nilai 0 ke
closure
variabel dan menulis ulang fungsi untuk:Dan karena
alertValue
membutuhkan variabel lokalclosure
untuk menjalankan fungsi, ia mengikat dirinya sendiri dengan nilai variabel lokal yang ditugaskan sebelumnyaclosure
.Dan sekarang setiap kali Anda memanggil
closure_example
fungsi, itu akan menuliskan nilaiclosure
variabel yang bertambah karenaalert(closure)
terikat.sumber
"Penutupan" pada dasarnya adalah beberapa negara bagian dan beberapa kode, digabungkan menjadi satu paket. Biasanya, keadaan lokal berasal dari lingkup (leksikal) di sekitarnya dan kode tersebut (pada dasarnya) merupakan fungsi dalam yang kemudian dikembalikan ke luar. Penutupan kemudian merupakan kombinasi dari variabel yang ditangkap yang dilihat fungsi dalam dan kode fungsi dalam.
Ini salah satu hal yang, sayangnya, agak sulit dijelaskan, karena tidak terbiasa.
Salah satu analogi yang berhasil saya gunakan di masa lalu adalah "bayangkan kita memiliki sesuatu yang kita sebut 'buku', di penutupan kamar, 'buku' adalah salinan itu di sana, di sudut, dari TAOCP, tetapi di meja-penutupan , itu salinan buku File Dresden. Jadi, tergantung pada penutupan apa Anda, kode 'beri saya buku' menghasilkan hal-hal yang berbeda terjadi. "
sumber
static
variabel lokal dianggap sebagai penutupan? Apakah penutupan di Haskell melibatkan negara?static
variabel lokal, Anda memiliki tepat satu).Sulit untuk mendefinisikan apa penutupan itu tanpa mendefinisikan konsep 'negara'.
Pada dasarnya, dalam bahasa dengan pelingkupan leksikal penuh yang memperlakukan fungsi sebagai nilai kelas satu, sesuatu yang istimewa terjadi. Jika saya melakukan sesuatu seperti:
Variabel
x
tidak hanya referensifunction foo()
tetapi juga referensi negarafoo
dibiarkan terakhir kali dikembalikan. Keajaiban nyata terjadi ketikafoo
fungsi-fungsi lain didefinisikan lebih lanjut dalam ruang lingkupnya; itu seperti lingkungan mini sendiri (seperti 'biasanya' kita mendefinisikan fungsi dalam lingkungan global).Secara fungsional ia dapat memecahkan banyak masalah yang sama dengan kata kunci 'statis' C ++ (C?), Yang mempertahankan status variabel lokal melalui beberapa panggilan fungsi; namun itu lebih seperti menerapkan prinsip yang sama (variabel statis) ke suatu fungsi, karena fungsi adalah nilai kelas pertama; closure menambahkan dukungan untuk seluruh fungsi keadaan untuk disimpan (tidak ada hubungannya dengan fungsi statis C ++).
Memperlakukan fungsi sebagai nilai kelas pertama dan menambahkan dukungan untuk penutupan juga berarti Anda dapat memiliki lebih dari satu instance dari fungsi yang sama dalam memori (mirip dengan kelas). Apa artinya ini adalah Anda dapat menggunakan kembali kode yang sama tanpa harus mengatur ulang status fungsi, seperti yang diperlukan ketika berhadapan dengan variabel statis C ++ di dalam suatu fungsi (mungkin salah tentang ini?).
Berikut ini beberapa pengujian dukungan penutupan Lua.
hasil:
Ini bisa menjadi rumit, dan mungkin bervariasi dari satu bahasa ke bahasa lain, tetapi tampaknya di Lua bahwa setiap kali suatu fungsi dieksekusi, statusnya diatur ulang. Saya mengatakan ini karena hasil dari kode di atas akan berbeda jika kita mengakses
myclosure
fungsi / negara secara langsung (alih-alih melalui fungsi anonim mengembalikannya), sepertipvalue
akan diatur ulang kembali ke 10; tetapi jika kita mengakses status myclosure melalui x (fungsi anonim) Anda dapat melihat bahwapvalue
itu hidup dan berada di suatu tempat di memori. Saya menduga ada sedikit lebih dari itu, mungkin seseorang dapat lebih menjelaskan sifat implementasi.PS: Saya tidak tahu sedikitpun tentang C ++ 11 (selain apa yang ada di versi sebelumnya) jadi perlu dicatat bahwa ini bukan perbandingan antara penutupan di C ++ 11 dan Lua. Juga, semua 'garis yang ditarik' dari Lua ke C ++ adalah kesamaan karena variabel statis dan penutupan tidak 100% sama; bahkan jika mereka kadang-kadang digunakan untuk memecahkan masalah yang sama.
Hal yang saya tidak yakin adalah, dalam contoh kode di atas, apakah fungsi anonim atau fungsi urutan lebih tinggi dianggap sebagai penutupan?
sumber
Penutupan adalah fungsi yang memiliki status terkait:
Dalam perl Anda membuat penutupan seperti ini:
Jika kita melihat fungsionalitas baru yang disediakan dengan C ++.
Ini juga memungkinkan Anda untuk mengikat keadaan saat ini ke objek:
sumber
Mari kita pertimbangkan fungsi sederhana:
Fungsi ini disebut fungsi tingkat atas karena tidak bersarang di dalam fungsi lain. Setiap fungsi JavaScript terkait dengan dirinya sendiri daftar objek yang disebut "Rantai Lingkup" . Rantai lingkup ini adalah daftar objek yang diurutkan. Setiap objek ini mendefinisikan beberapa variabel.
Dalam fungsi tingkat atas, rantai lingkup terdiri dari satu objek, objek global. Sebagai contoh, fungsi di
f1
atas memiliki rantai lingkup yang memiliki objek tunggal di dalamnya yang mendefinisikan semua variabel global. (perhatikan bahwa istilah "objek" di sini tidak berarti objek JavaScript, itu hanya objek implementasi yang didefinisikan yang bertindak sebagai wadah variabel, di mana JavaScript dapat "mencari" variabel.)Ketika fungsi ini dipanggil, JavaScript membuat sesuatu yang disebut "objek Aktivasi" , dan meletakkannya di bagian atas rantai lingkup. Objek ini berisi semua variabel lokal (misalnya di
x
sini). Karenanya sekarang kita memiliki dua objek dalam rantai lingkup: yang pertama adalah objek aktivasi dan di bawahnya adalah objek global.Catat dengan sangat hati-hati bahwa kedua objek dimasukkan ke dalam rantai lingkup pada waktu yang BERBEDA. Objek global diletakkan ketika fungsi didefinisikan (yaitu, ketika JavaScript mem-parsing fungsi dan membuat objek fungsi), dan objek aktivasi masuk ketika fungsi dipanggil.
Jadi, kita sekarang tahu ini:
Situasi menjadi menarik ketika kita berurusan dengan fungsi bersarang. Jadi, mari kita buat satu:
Ketika
f1
didefinisikan, kita mendapatkan rantai lingkup karena hanya berisi objek global.Sekarang ketika
f1
dipanggil, rantai lingkupf1
mendapat objek aktivasi. Objek aktivasi ini berisi variabelx
dan variabelf2
yang merupakan fungsi. Dan, perhatikan bahwaf2
semakin didefinisikan. Karenanya, pada titik ini, JavaScript juga menyimpan rantai cakupan baru untukf2
. Rantai lingkup disimpan untuk fungsi dalam ini adalah rantai lingkup saat ini berlaku. Rantai lingkup saat ini yang berlaku adalah darif1
. Oleh karena ituf2
's rantai lingkup adalahf1
' s saat ini ruang lingkup rantai - yang berisi objek aktivasif1
dan obyek global.Ketika
f2
dipanggil, ia mendapatkan objek aktivasi itu sendiri berisiy
, ditambahkan ke rantai lingkupnya yang sudah berisi objek aktivasif1
dan objek global.Jika ada fungsi bersarang lain yang didefinisikan di dalam
f2
, rantai cakupannya akan berisi tiga objek pada waktu definisi (2 objek aktivasi dari dua fungsi luar, dan objek global), dan 4 pada waktu doa.Jadi, sekarang kami mengerti bagaimana rantai lingkup bekerja tetapi kami belum membicarakan tentang penutupan.
Sebagian besar fungsi dipanggil menggunakan rantai lingkup yang sama yang berlaku ketika fungsi didefinisikan, dan tidak terlalu penting bahwa ada penutupan yang terlibat. Penutupan menjadi menarik ketika mereka dipanggil di bawah rantai lingkup yang berbeda dari yang berlaku ketika mereka didefinisikan. Ini terjadi paling umum ketika objek fungsi bersarang dikembalikan dari fungsi yang didefinisikan.
Ketika fungsi kembali, objek aktivasi itu dihapus dari rantai lingkup. Jika tidak ada fungsi bersarang, tidak ada lagi referensi ke objek aktivasi dan mengumpulkan sampah. Jika ada fungsi bersarang yang ditentukan, maka masing-masing fungsi tersebut memiliki referensi ke rantai lingkup, dan rantai lingkup tersebut merujuk ke objek aktivasi.
Namun, jika objek fungsi bersarang tetap berada di dalam fungsi luarnya, maka objek itu sendiri akan menjadi sampah yang dikumpulkan, bersama dengan objek aktivasi yang dimaksud. Tetapi jika fungsi mendefinisikan fungsi bersarang dan mengembalikannya atau menyimpannya di properti di suatu tempat, maka akan ada referensi eksternal ke fungsi bersarang. Itu bukan sampah yang dikumpulkan, dan objek aktivasi yang dimaksud juga bukan sampah yang dikumpulkan.
Dalam contoh kami di atas, kami tidak kembali
f2
darif1
, karenanya, ketika panggilan untukf1
kembali, objek aktivasi akan dihapus dari rantai cakupannya dan sampah dikumpulkan. Tetapi jika kita memiliki sesuatu seperti ini:Di sini, pengembalian
f2
akan memiliki rantai lingkup yang akan berisi objek aktivasif1
, dan karenanya itu tidak akan menjadi sampah yang dikumpulkan. Pada titik ini, jika kita memanggilf2
, ia akan dapat mengaksesf1
variabelx
meskipun kita tidak adaf1
.Oleh karena itu kita dapat melihat bahwa fungsi menjaga rantai ruang lingkup dengannya dan dengan rantai lingkup datang semua objek aktivasi fungsi luar. Inilah inti dari penutupan. Kami mengatakan bahwa fungsi dalam JavaScript adalah "dibatasi secara leksikal" , yang berarti bahwa mereka menyimpan ruang lingkup yang aktif ketika didefinisikan sebagai bertentangan dengan ruang lingkup yang aktif ketika mereka dipanggil.
Ada sejumlah teknik pemrograman yang kuat yang melibatkan penutupan seperti mendekati variabel pribadi, pemrograman berbasis peristiwa, aplikasi parsial , dll.
Perhatikan juga bahwa semua ini berlaku untuk semua bahasa yang mendukung penutupan. Misalnya PHP (5.3+), Python, Ruby, dll.
sumber
Penutupan adalah pengoptimalan kompiler (alias sintaksis gula?). Beberapa orang menyebut ini sebagai Obyek Orang Miskin juga.
Lihat jawabannya oleh Eric Lippert : (kutipan di bawah)
Kompiler akan menghasilkan kode seperti ini:
Masuk akal?
Anda juga meminta perbandingan. VB dan JScript keduanya membuat penutupan dengan cara yang hampir sama.
sumber