it-swarm-id.com

Apa itu penutupan?

Sesekali saya melihat "penutupan" disebutkan, dan saya mencoba mencarinya tetapi Wiki tidak memberikan penjelasan yang saya mengerti. Bisakah seseorang membantu saya di sini?

157
gablin

(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 satu"), yang memiliki kemampuan khusus untuk mengakses variabel lain secara lokal ke ruang lingkup tempat itu 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();

Fungsinya1 ditugaskan untuk document.onclick dan displayValOfBlack adalah penutupan. Anda dapat melihat bahwa keduanya merujuk variabel boolean black, tetapi variabel tersebut ditugaskan di luar fungsi. Karena black adalah lokal ke ruang lingkup di mana fungsi didefinisikan , pointer ke variabel ini dipertahankan.

Jika Anda meletakkan ini di halaman HTML:

  1. Klik untuk berubah menjadi hitam
  2. Tekan [enter] untuk melihat "true"
  3. Klik lagi, ubah kembali menjadi putih
  4. Tekan [enter] untuk melihat "false"

Ini menunjukkan bahwa keduanya memiliki akses ke sama black, dan dapat digunakan untuk menyimpan status tanpa objek pembungkus apa pun.

Panggilan ke setKeyPress adalah untuk menunjukkan bagaimana suatu fungsi dapat dilewatkan seperti halnya variabel apa pun. scope yang diawetkan dalam closure masih merupakan fungsi yang 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 tidak hati-hati akan menyebabkan kebocoran memori (seperti ketika event handler yang tidak digunakan namun diawetkan adalah satu-satunya hal yang bisa dipegang oleh objek besar dalam memori, terutama objek DOM, yang mencegah pengumpulan sampah).


1: Sebenarnya, semua fungsi dalam JavaScript adalah penutup.

141
Nicole

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 metode (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. Sebaliknya, 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 merupakan objek di bawah tenda, Anda menyebarkannya sebagai referensi fungsi, karena fokusnya adalah pada sifatnya sebagai fungsi.

69
Mason Wheeler

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 yang menunjukkan dua nilai integer. Nama v terikat karena dideklarasikan sebagai argumen dari fungsi addConstant (dengan melihat deklarasi fungsi kita tahu bahwa v akan diberi nilai ketika fungsi tersebut dipanggil). Nama k adalah bebas fungsi wrt addConstant karena fungsi tidak berisi petunjuk tentang nilai apa k terikat ke (dan bagaimana).

Untuk mengevaluasi panggilan seperti:

val n = addConstant(10)

kita harus menetapkan k nilai, yang hanya dapat terjadi jika nama k didefinisikan dalam konteks di mana 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 closure 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 adalah 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, mis.

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  values.map(v => v + k)
}

Perhatikan bahwa fungsi tanpa variabel bebas adalah kasus khusus 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.

29
Giorgio

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 closure yang sebelumnya dibuat. Namespace fungsi alertValue yang dikembalikan akan dihubungkan ke namespace tempat variabel closure berada. Ketika Anda menghapus seluruh fungsi, nilai variabel closure akan dihapus, tetapi sampai saat itu, fungsi alertValue akan selalu dapat membaca/menulis nilai variabel closure.

Jika Anda menjalankan kode ini, iterasi pertama akan memberikan nilai 0 ke variabel closure dan menulis ulang fungsinya menjadi:

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 sebelumnya ditetapkan closure.

Dan sekarang setiap kali Anda memanggil fungsi closure_example, Ia akan menuliskan nilai yang bertambah dari variabel closure karena alert(closure) terikat.

closure_example.alertValue()//alerts value 1 
closure_example.alertValue()//alerts value 2 
closure_example.alertValue()//alerts value 3
//etc. 
9
Muha

"Penutupan" pada dasarnya adalah beberapa negara bagian dan beberapa kode, digabungkan menjadi satu paket. Biasanya, kondisi lokal berasal dari lingkup (leksikal) di sekitarnya dan kode tersebut (pada dasarnya) adalah fungsi dalam yang kemudian dikembalikan ke luar. Penutupan kemudian merupakan kombinasi dari variabel yang ditangkap yang dilihat fungsi dalam dan kode fungsi dalam.

Itu 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. "

5
Vatine

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 keadaan foo ditinggalkan di terakhir kali ia kembali. Keajaiban nyata terjadi ketika foo memiliki fungsi-fungsi lain yang didefinisikan lebih lanjut dalam ruang lingkupnya; itu seperti lingkungan mini sendiri (seperti 'biasanya' kami 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 status fungsi 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 adalah 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 fungsi/negara myclosure secara langsung (alih-alih melalui fungsi anonim yang dikembalikan), karena pvalue akan diatur ulang ke 10; tetapi jika kita mengakses status myclosure melalui x (fungsi anonim) Anda dapat melihat bahwa pvalue hidup dan berada di suatu tempat di memori. Saya kira 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?

5
Trae Barlow

Penutupan adalah fungsi yang memiliki status terkait:

Di 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 status 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");
}
4
Martin York

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 mengaitkan 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. Misalnya, fungsi f1 Di 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 x di 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 (mis., Ketika JavaScript mem-parsing fungsi dan membuat objek fungsi), dan objek aktivasi masuk ketika fungsi dipanggil.

Jadi, kita sekarang tahu ini:

  • Setiap fungsi memiliki rantai lingkup yang terkait dengannya
  • Ketika fungsi didefinisikan (ketika objek fungsi dibuat), JavaScript menyimpan rantai lingkup dengan fungsi itu
  • Untuk fungsi tingkat atas, rantai lingkup hanya berisi objek global pada waktu definisi fungsi dan menambahkan objek aktivasi tambahan di atas pada waktu doa

Situasi menjadi menarik ketika kita berurusan dengan fungsi bersarang. Jadi, mari kita buat satu:

function f1(x) {

    function f2(y) {
        // ... something
    }

}

Ketika f1 Didefinisikan, kami mendapatkan rantai lingkup karena hanya berisi objek global.

Sekarang ketika f1 Dipanggil, rantai lingkup f1 Mendapatkan objek aktivasi. Objek aktivasi ini berisi variabel x dan variabel f2 Yang merupakan fungsi. Dan, perhatikan bahwa f2 Sedang 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 berlaku adalah itu dari f1. Karenanya rantai lingkup f2 Adalah f1 rantai lingkup saat ini - yang berisi objek aktivasi f1 Dan objek global.

Ketika f2 Dipanggil, ia mendapatkan objek aktivasi sendiri yang mengandung y, ditambahkan ke rantai cakupannya yang sudah berisi objek aktivasi f1 Dan objek global.

Jika ada fungsi bersarang lain yang didefinisikan 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 itu didefinisikan, dan itu tidak masalah bahwa ada penutupan yang terlibat. Penutupan menjadi menarik ketika mereka dipanggil di bawah rantai lingkup yang berbeda dari yang berlaku saat didefinisikan. Ini terjadi paling umum ketika objek fungsi bersarang adalah 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 itu mengumpulkan sampah. Jika ada fungsi bersarang yang didefinisikan, 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 tidak akan menjadi sampah yang dikumpulkan, dan objek aktivasi yang dimaksud juga tidak akan menjadi sampah yang dikumpulkan.

Dalam contoh di atas, kami tidak mengembalikan f2 Dari f1, Karenanya, ketika panggilan ke f1 Kembali, objek aktivasi akan dihapus dari rantai ruang lingkup 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, Itu akan dapat mengakses variabel f1x meskipun kita keluar dari f1.

Oleh karena itu kita dapat melihat bahwa suatu fungsi menjaga rantai lingkupnya bersamanya dan dengan rantai cakupan datang semua objek aktivasi fungsi luar. Inilah inti dari penutupan. Kami mengatakan bahwa fungsi dalam JavaScript adalah "scoped lexically" , 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 acara didorong, aplikasi parsial , dll.

Perhatikan juga bahwa semua ini berlaku untuk semua bahasa yang mendukung penutupan. Misalnya PHP (5.3+), Python, Ruby, dll.

2
treecoder