Sesekali saya melihat "penutupan" disebutkan, dan saya mencoba mencarinya tetapi Wiki tidak memberikan penjelasan yang saya mengerti. Bisakah seseorang membantu saya di sini?
Sesekali saya melihat "penutupan" disebutkan, dan saya mencoba mencarinya tetapi Wiki tidak memberikan penjelasan yang saya mengerti. Bisakah seseorang membantu saya di sini?
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):
var setKeyPress = function(callback) {
document.onkeypress = callback;
};
var initialize = function() {
var black = false;
document.onclick = function() {
black = !black;
document.body.style.backgroundColor = black ? "#000000" : "transparent";
}
var displayValOfBlack = function() {
alert(black);
}
setKeyPress(displayValOfBlack);
};
initialize();
Fungsi 1 ditugaskan untuk document.onclick
dan displayValOfBlack
merupakan penutupan. Anda dapat melihat bahwa keduanya merujuk variabel boolean black
, tetapi variabel tersebut ditugaskan di luar fungsi. Karena black
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.
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.
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:
def addConstant(v: Int): Int = v + k
Dalam tubuh fungsi ada dua nama (variabel) v
dan k
menunjukkan dua nilai integer. Nama v
diikat karena dideklarasikan sebagai argumen fungsi addConstant
(dengan melihat deklarasi fungsi kita tahu bahwa v
akan diberi nilai ketika fungsi dipanggil). Namanya k
adalah free wrt fungsi addConstant
karena fungsi tidak mengandung petunjuk tentang apa nilai k
terikat (dan bagaimana).
Untuk mengevaluasi panggilan seperti:
val n = addConstant(10)
kita harus menetapkan k
nilai, yang hanya dapat terjadi jika nama k
tersebut didefinisikan dalam konteks yang addConstant
didefinisikan. Sebagai contoh:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
def addConstant(v: Int): Int = v + k
values.map(addConstant)
}
Sekarang kita telah mendefinisikan addConstant
dalam konteks di mana k
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 bebas k
terikat ke nilai ketika penutupan didefinisikan , sedangkan variabel argumen v
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
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
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.
Penjelasan sederhana dalam JavaScript:
var closure_example = function() {
var closure = 0;
// after first iteration the value will not be erased from the memory
// because it is bound with the returned alertValue function.
return {
alertValue : function() {
closure++;
alert(closure);
}
};
};
closure_example();
alert(closure)
akan menggunakan nilai yang dibuat sebelumnya dari closure
. alertValue
Namespace fungsi yang dikembalikan akan terhubung ke namespace di mana closure
variabel berada. Ketika Anda menghapus seluruh fungsi, nilai closure
variabel akan dihapus, tetapi sampai saat itu, alertValue
fungsi akan selalu dapat membaca / menulis nilai variabel closure
.
Jika Anda menjalankan kode ini, iterasi pertama akan memberikan nilai 0 ke closure
variabel dan menulis ulang fungsi untuk:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
Dan karena alertValue
membutuhkan variabel lokal closure
untuk menjalankan fungsi, ia mengikat dirinya sendiri dengan nilai variabel lokal yang ditugaskan sebelumnya closure
.
Dan sekarang setiap kali Anda memanggil closure_example
fungsi, itu akan menuliskan nilai closure
variabel yang bertambah karena alert(closure)
terikat.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
"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. "
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:
function foo(x)
return x
end
x = foo
Variabel x
tidak hanya referensi function foo()
tetapi juga referensi negara foo
dibiarkan terakhir kali dikembalikan. Keajaiban nyata terjadi ketika foo
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.
--Closure testing
--By Trae Barlow
--
function myclosure()
print(pvalue)--nil
local pvalue = pvalue or 10
return function()
pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
print(pvalue)
pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
return pvalue
end
end
x = myclosure() --x now references anonymous function inside myclosure()
x()--nil, 20
x() --21, 31
x() --32, 42
--43, 53 -- if we iterated x() again
hasil:
nil
20
31
42
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), seperti pvalue
akan diatur ulang kembali ke 10; tetapi jika kita mengakses status myclosure melalui x (fungsi anonim) Anda dapat melihat bahwa pvalue
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?
Penutupan adalah fungsi yang memiliki status terkait:
Dalam perl Anda membuat penutupan seperti ini:
#!/usr/bin/perl
# This function creates a closure.
sub getHelloPrint
{
# Bind state for the function we are returning.
my ($first) = @_;a
# The function returned will have access to the variable $first
return sub { my ($second) = @_; print "$first $second\n"; };
}
my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");
&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World
Jika kita melihat fungsionalitas baru yang disediakan dengan C ++.
Ini juga memungkinkan Anda untuk mengikat keadaan saat ini ke objek:
#include <string>
#include <iostream>
#include <functional>
std::function<void(std::string const&)> getLambda(std::string const& first)
{
// Here we bind `first` to the function
// The second parameter will be passed when we call the function
return [first](std::string const& second) -> void
{ std::cout << first << " " << second << "\n";
};
}
int main(int argc, char* argv[])
{
auto hw = getLambda("Hello");
auto gw = getLambda("GoodBye");
hw("World");
gw("World");
}
Mari kita pertimbangkan fungsi sederhana:
function f1(x) {
// ... something
}
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:
function f1(x) {
function f2(y) {
// ... something
}
}
Ketika f1
didefinisikan, kita mendapatkan rantai lingkup karena hanya berisi objek global.
Sekarang ketika f1
dipanggil, rantai lingkup f1
mendapat objek aktivasi. Objek aktivasi ini berisi variabel x
dan variabel f2
yang merupakan fungsi. Dan, perhatikan bahwa f2
semakin didefinisikan. Karenanya, pada titik ini, JavaScript juga menyimpan rantai cakupan baru untuk f2
. Rantai lingkup disimpan untuk fungsi dalam ini adalah rantai lingkup saat ini berlaku. Rantai lingkup saat ini yang berlaku adalah dari f1
. Oleh karena itu f2
's rantai lingkup adalah f1
' s saat ini ruang lingkup rantai - yang berisi objek aktivasi f1
dan obyek global.
Ketika f2
dipanggil, ia mendapatkan objek aktivasi itu sendiri berisi y
, ditambahkan ke rantai lingkupnya yang sudah berisi objek aktivasi f1
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.
Kombinasi objek fungsi dan ruang lingkup (satu set binding variabel) di mana variabel fungsi diselesaikan disebut penutupan dalam literatur ilmu komputer - JavaScript panduan definitif oleh David Flanagan
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
dari f1
, karenanya, ketika panggilan untuk f1
kembali, objek aktivasi akan dihapus dari rantai cakupannya dan sampah dikumpulkan. Tetapi jika kita memiliki sesuatu seperti ini:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Di sini, pengembalian f2
akan memiliki rantai lingkup yang akan berisi objek aktivasi f1
, dan karenanya itu tidak akan menjadi sampah yang dikumpulkan. Pada titik ini, jika kita memanggil f2
, ia akan dapat mengakses f1
variabel x
meskipun kita tidak ada f1
.
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.
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:
private class Locals
{
public int count;
public void Anonymous()
{
this.count++;
}
}
public Action Counter()
{
Locals locals = new Locals();
locals.count = 0;
Action counter = new Action(locals.Anonymous);
return counter;
}
Masuk akal?
Anda juga meminta perbandingan. VB dan JScript keduanya membuat penutupan dengan cara yang hampir sama.