Pertanyaan Bagaimana cara kerja JavaScript ditutup?


Bagaimana Anda menjelaskan penutupan JavaScript kepada seseorang dengan pengetahuan tentang konsep yang mereka terdiri dari (misalnya fungsi, variabel dan sejenisnya), tetapi tidak memahami penutupan diri mereka sendiri?

saya sudah melihat contoh Skema diberikan di Wikipedia, tapi sayangnya itu tidak membantu.


7654


asal


Jawaban:


Penutupan JavaScript untuk pemula

Dikirimkan oleh Morris pada Tue, 2006-02-21 10:19. Diedit oleh komunitas sejak.

Penutupan bukan sihir

Halaman ini menjelaskan penutupan sehingga programmer dapat memahaminya - menggunakan kode JavaScript yang berfungsi. Ini bukan untuk guru atau programmer fungsional.

Penutupan adalah tidak sulit untuk memahami sekali konsep inti adalah grokked. Namun, mereka tidak mungkin memahami dengan membaca makalah akademis atau informasi yang berorientasi akademis tentang mereka!

Artikel ini ditujukan untuk programmer dengan beberapa pengalaman pemrograman dalam bahasa utama, dan siapa yang dapat membaca fungsi JavaScript berikut:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Contoh penutupan

Dua ringkasan satu kalimat:

  • Penutupan adalah salah satu cara untuk mendukung fungsi kelas satu; itu adalah ekspresi yang dapat mereferensikan variabel dalam ruang lingkupnya (ketika pertama kali dinyatakan), ditetapkan ke variabel, dilewatkan sebagai argumen ke fungsi, atau dikembalikan sebagai hasil fungsi.

  • Atau, penutupan adalah bingkai tumpukan yang dialokasikan ketika suatu fungsi memulai eksekusinya, dan tidak dibebaskan setelah fungsi kembali (seolah-olah 'stack frame' dialokasikan pada heap daripada stack!).

Kode berikut mengembalikan referensi ke suatu fungsi:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

Sebagian besar pemrogram JavaScript akan memahami bagaimana referensi ke fungsi dikembalikan ke variabel (say2) dalam kode di atas. Jika tidak, maka Anda perlu melihat itu sebelum Anda dapat mempelajari penutupan. Seorang programmer yang menggunakan C akan menganggap fungsi sebagai mengembalikan pointer ke fungsi, dan bahwa variabel say dan say2 masing-masing pointer ke fungsi.

Ada perbedaan penting antara penunjuk C ke fungsi dan referensi JavaScript ke fungsi. Dalam JavaScript, Anda bisa memikirkan variabel referensi fungsi sebagai memiliki kedua pointer ke fungsi demikian juga sebagai penunjuk tersembunyi untuk penutupan.

Kode di atas memiliki penutupan karena fungsi anonim function() { console.log(text); } dinyatakan dalam fungsi lain, sayHello2() dalam contoh ini. Dalam JavaScript, jika Anda menggunakan function kata kunci di dalam fungsi lain, Anda membuat penutupan.

Di C dan sebagian besar bahasa umum lainnya, setelah fungsi kembali, semua variabel lokal tidak lagi dapat diakses karena stack-frame dihancurkan.

Dalam JavaScript, jika Anda mendeklarasikan fungsi dalam fungsi lain, maka variabel lokal dapat tetap dapat diakses setelah kembali dari fungsi yang Anda panggil. Ini ditunjukkan di atas, karena kita memanggil fungsi say2()setelah kami kembali dari sayHello2(). Perhatikan bahwa kode yang kita sebut referensi variabel text, yang mana a variabel lokal dari fungsi tersebut sayHello2().

function() { console.log(text); } // Output of say2.toString();

Melihat output dari say2.toString(), kita dapat melihat bahwa kode mengacu pada variabel text. Fungsi anonim dapat referensi text yang memegang nilai 'Hello Bob' karena variabel lokal sayHello2() disimpan dalam penutupan.

Keajaibannya adalah bahwa dalam JavaScript referensi fungsi juga memiliki referensi rahasia untuk penutupan dibuat dalam - mirip dengan bagaimana delegasi adalah pointer metode ditambah referensi rahasia ke objek.

Lebih banyak contoh

Untuk beberapa alasan, penutupan tampaknya sangat sulit untuk dipahami ketika Anda membaca tentang mereka, tetapi ketika Anda melihat beberapa contoh menjadi jelas bagaimana mereka bekerja (butuh beberapa saat). Saya sarankan untuk mengerjakan contoh-contoh ini dengan saksama sampai Anda memahami cara kerjanya. Jika Anda mulai menggunakan penutupan tanpa sepenuhnya memahami cara kerjanya, Anda akan segera membuat beberapa bug yang sangat aneh!

Contoh 3

Contoh ini menunjukkan bahwa variabel lokal tidak disalin - mereka disimpan oleh referensi. Ini seperti menjaga tumpukan-frame dalam memori ketika fungsi luar keluar!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Contoh 4

Ketiga fungsi global memiliki referensi umum ke sama penutupan karena semuanya dinyatakan dalam satu panggilan ke setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Ketiga fungsi tersebut memiliki akses bersama ke penutupan yang sama - variabel lokal setupSomeGlobals() ketika tiga fungsi didefinisikan.

Perhatikan bahwa dalam contoh di atas, jika Anda menelepon setupSomeGlobals() lagi, maka penutupan baru (stack-frame!) dibuat. Yang lama gLogNumber, gIncreaseNumber, gSetNumber variabel ditimpa dengan baru fungsi yang memiliki penutupan baru. (Dalam JavaScript, setiap kali Anda mendeklarasikan fungsi di dalam fungsi lain, fungsi di dalam akan dibuat ulang setiap mengatur waktu fungsi luar.)

Contoh 5

Contoh ini menunjukkan bahwa penutupan mengandung variabel lokal yang dideklarasikan di dalam fungsi luar sebelum keluar. Perhatikan bahwa variabel alice sebenarnya dinyatakan setelah fungsi anonim. Fungsi anonim dideklarasikan terlebih dahulu; dan ketika fungsi itu disebut dapat mengakses alice variabel karena alice berada dalam lingkup yang sama (JavaScript tidak pengangkatan variabel). Juga sayAlice()() langsung memanggil referensi fungsi kembali sayAlice() - Ini persis sama dengan yang dilakukan sebelumnya tetapi tanpa variabel sementara.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: perhatikan juga bahwa say variabel juga di dalam penutupan, dan dapat diakses oleh fungsi lain yang mungkin dideklarasikan di dalamnya sayAlice(), atau bisa diakses secara rekursif di dalam fungsi dalam.

Contoh 6

Yang satu ini sangat nyata bagi banyak orang, jadi Anda harus memahaminya. Berhati-hatilah jika Anda mendefinisikan suatu fungsi dalam satu lingkaran: variabel lokal dari penutupan mungkin tidak bertindak seperti yang mungkin Anda pikirkan sebelumnya.

Anda perlu memahami fitur "variable hoisting" di Javascript untuk memahami contoh ini.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

Garis result.push( function() {console.log(item + ' ' + list[i])} menambahkan referensi ke fungsi anonim tiga kali ke array hasil. Jika Anda tidak begitu akrab dengan fungsi anonim, pikirkan itu seperti:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Perhatikan bahwa ketika Anda menjalankan contoh, "item2 undefined" dicatat tiga kali! Ini karena sama seperti contoh sebelumnya, hanya ada satu penutupan untuk variabel lokal buildList (yang mana result, i dan item). Ketika fungsi anonim dipanggil di telepon fnlist[j](); mereka semua menggunakan penutupan tunggal yang sama, dan mereka menggunakan nilai saat ini untuk i dan item dalam satu penutupan itu (di mana i memiliki nilai 3 karena loop telah selesai, dan item memiliki nilai 'item2'). Perhatikan kami mengindeks dari 0 karenanya item memiliki nilai item2. Dan i ++ akan bertambah i dengan nilai 3.

Mungkin berguna untuk melihat apa yang terjadi ketika deklarasi tingkat blok variabel item digunakan (melalui let kata kunci) daripada deklarasi variabel yang mencakup fungsi melalui var kata kunci. Jika perubahan itu dibuat, maka setiap fungsi anonim dalam larik result memiliki penutupan sendiri; ketika contoh dijalankan, hasilnya adalah sebagai berikut:

item0 undefined
item1 undefined
item2 undefined

Jika variabelnya i juga didefinisikan menggunakan let dari pada var, maka hasilnya adalah:

item0 1
item1 2
item2 3

Contoh 7

Dalam contoh terakhir ini, setiap panggilan ke fungsi utama menciptakan penutupan terpisah.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Ringkasan

Jika semuanya tampak benar-benar tidak jelas maka hal terbaik untuk dilakukan adalah bermain dengan contoh-contoh. Membaca penjelasan jauh lebih sulit daripada memahami contoh. Penjelasan saya tentang penutupan dan tumpukan frame, dll. Secara teknis tidak benar - mereka adalah penyederhanaan kotor yang dimaksudkan untuk membantu memahami. Setelah ide dasar dikerjakan, Anda dapat mengambil detailnya nanti.

Poin terakhir:

  • Kapanpun kamu menggunakannya function di dalam fungsi lain, penutupan digunakan.
  • Kapanpun kamu menggunakannya eval() di dalam fungsi, penutupan digunakan. Teks Anda eval dapat mereferensikan variabel lokal dari fungsi, dan dalam eval Anda bahkan dapat membuat variabel lokal baru dengan menggunakan eval('var foo = …')
  • Ketika Anda menggunakan new Function(…) (itu Konstruktor fungsi) di dalam fungsi, itu tidak membuat penutupan. (Fungsi baru tidak dapat mereferensikan variabel lokal dari fungsi luar.)
  • Penutupan JavaScript sama seperti menyimpan salinan semua variabel lokal, sama seperti ketika ada fungsi yang keluar.
  • Mungkin lebih baik untuk berpikir bahwa penutupan selalu dibuat hanya sebuah entri ke fungsi, dan variabel lokal ditambahkan ke penutupan itu.
  • Satu set variabel lokal baru disimpan setiap kali fungsi dengan penutupan disebut (mengingat bahwa fungsi tersebut berisi deklarasi fungsi di dalamnya, dan referensi ke fungsi di dalamnya dikembalikan atau referensi eksternal disimpan untuk itu dalam beberapa cara ).
  • Dua fungsi mungkin terlihat seperti mereka memiliki teks sumber yang sama, tetapi memiliki perilaku yang sama sekali berbeda karena 'penutupan tersembunyi' mereka. Saya tidak berpikir kode JavaScript benar-benar dapat mengetahui apakah referensi fungsi memiliki penutupan atau tidak.
  • Jika Anda mencoba melakukan modifikasi kode sumber dinamis (misalnya: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), itu tidak akan berhasil jika myFunction adalah penutupan (tentu saja, Anda tidak akan pernah berpikir untuk melakukan substitusi string kode sumber pada saat runtime, tapi ...).
  • Adalah mungkin untuk mendapatkan deklarasi fungsi dalam deklarasi fungsi dalam fungsi - dan Anda bisa mendapatkan penutupan pada lebih dari satu level.
  • Saya pikir biasanya penutupan adalah istilah untuk kedua fungsi bersama dengan variabel yang ditangkap. Perhatikan bahwa saya tidak menggunakan definisi itu dalam artikel ini!
  • Saya menduga bahwa penutupan dalam JavaScript berbeda dari yang biasanya ditemukan dalam bahasa fungsional.

Tautan

Terima kasih

Jika Anda memiliki hanya penutupan belajar (di sini atau di tempat lain!), maka saya tertarik pada setiap masukan dari Anda tentang perubahan apa pun yang mungkin Anda sarankan yang dapat membuat artikel ini lebih jelas. Kirim email ke morrisjohns.com (morris_closure @). Harap dicatat bahwa saya bukan seorang guru di JavaScript - atau pada penutupan.


Posting asli oleh Morris dapat ditemukan di Arsip Internet.


6160



Setiap kali Anda melihat kata kunci fungsi dalam fungsi lain, fungsi batin memiliki akses ke variabel di fungsi luar.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Ini akan selalu mencatat 16, karena bar dapat mengakses x yang didefinisikan sebagai argumen untuk foo, dan itu juga bisa diakses tmp dari foo.

Bahwa aku s sebuah penutupan. Suatu fungsi tidak harus kembali untuk disebut penutupan. Hanya mengakses variabel di luar lingkup leksikal langsung Anda menciptakan penutupan.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

Fungsi di atas juga akan mencatat 16, karena bar masih bisa merujuk x dan tmp, meskipun tidak lagi secara langsung di dalam ruang lingkup.

Namun, sejak itu tmp masih berkeliaran di dalam barPenutupan itu, juga sedang bertambah. Ini akan bertambah setiap kali Anda menelepon bar.

Contoh penutupan yang paling sederhana adalah ini:

var a = 10;
function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Ketika fungsi JavaScript dipanggil, konteks eksekusi baru dibuat. Bersama dengan argumen fungsi dan objek induk, konteks eksekusi ini juga menerima semua variabel yang dinyatakan di luar itu (dalam contoh di atas, baik 'a' dan 'b').

Adalah mungkin untuk membuat lebih dari satu fungsi penutupan, baik dengan mengembalikan daftar dari mereka atau dengan mengaturnya ke variabel global. Semua ini akan mengacu pada sama  x dan sama tmp, mereka tidak membuat salinannya sendiri.

Di sini nomornya x adalah angka literal. Seperti literal lainnya di JavaScript, kapan foo disebut, nomornya x aku s disalin ke foo sebagai argumennya x.

Di sisi lain, JavaScript selalu menggunakan referensi ketika berhadapan dengan objek. Jika berkata, Anda menelepon foodengan objek, penutupan itu akan kembali referensi objek asli itu!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Seperti yang diharapkan, setiap panggilan ke bar(10) akan bertambah x.memb. Apa yang mungkin tidak diharapkan, apakah itu x hanya mengacu pada objek yang sama dengan age variabel! Setelah beberapa panggilan ke bar, age.memb akan menjadi 2! Referensi ini adalah dasar dari kebocoran memori dengan objek HTML.


3804



KATA PENGANTAR: jawaban ini ditulis ketika pertanyaannya adalah:

Seperti Albert yang lama berkata: "Jika Anda tidak dapat menjelaskannya kepada seorang anak berusia enam tahun, Anda benar-benar tidak memahaminya sendiri.". Saya mencoba menjelaskan penutupan JS kepada seorang teman berusia 27 tahun dan benar-benar gagal.

Adakah yang bisa menganggap bahwa saya 6 dan anehnya tertarik pada subjek itu?

Saya cukup yakin saya adalah satu-satunya orang yang mencoba untuk mengambil pertanyaan awal secara harfiah. Sejak itu, pertanyaan itu telah bermutasi beberapa kali, jadi jawaban saya sekarang mungkin tampak sangat konyol & tidak pada tempatnya. Semoga ide umum dari cerita ini tetap menyenangkan bagi sebagian orang.


Saya penggemar besar analogi dan metafor ketika menjelaskan konsep yang sulit, jadi biarkan saya mencoba tangan saya dengan sebuah cerita.

Pada suatu ketika:

Ada seorang putri ...

function princess() {

Dia tinggal di dunia yang indah penuh petualangan. Dia bertemu dengan Pangeran Tampan, berkeliling dunia dengan seekor naga bertanduk unicorn, bertempur, menemukan binatang yang bisa berbicara, dan banyak hal fantastis lainnya.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Tetapi dia harus selalu kembali ke pekerjaan dan tugasnya yang membosankan.

    return {

Dan dia sering memberi tahu mereka tentang petualangan menakjubkan terakhirnya sebagai seorang putri.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Tapi yang mereka lihat hanyalah seorang gadis kecil ...

var littleGirl = princess();

... bercerita tentang sihir dan fantasi.

littleGirl.story();

Dan meskipun orang dewasa tahu putri sejati, mereka tidak akan pernah percaya pada unicorn atau naga karena mereka tidak pernah bisa melihat mereka. Orang-orang dewasa mengatakan bahwa mereka hanya ada di dalam imajinasi gadis kecil itu.

Tetapi kita tahu kebenaran sejati; bahwa gadis kecil dengan Tuan Putri di dalam ...

... benar-benar seorang putri dengan seorang gadis kecil di dalam.


2251



Mempertimbangkan pertanyaan dengan serius, kita harus mencari tahu apa yang khas orang berusia 6 tahun yang mampu kognitif, meskipun diakui, orang yang tertarik pada JavaScript tidak begitu khas.

Di Perkembangan Anak: 5 hingga 7 Tahun  ia mengatakan:

Anak Anda akan dapat mengikuti petunjuk dua langkah. Misalnya, jika Anda berkata kepada anak Anda, "Pergi ke dapur dan ambilkan saya kantong sampah" mereka akan dapat mengingat arah itu.

Kita dapat menggunakan contoh ini untuk menjelaskan penutupan, sebagai berikut:

Dapur adalah penutupan yang memiliki variabel lokal, yang disebut trashBags. Ada fungsi di dalam dapur yang disebut getTrashBag yang mendapat satu kantong sampah dan mengembalikannya.

Kita bisa mengkodekan ini dalam JavaScript seperti ini:

function makeKitchen () {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

kitchen.getTrashBag(); // returns trash bag C
kitchen.getTrashBag(); // returns trash bag B
kitchen.getTrashBag(); // returns trash bag A

Poin lebih lanjut yang menjelaskan mengapa penutupan menarik:

  • Setiap kali makeKitchen() disebut, penutupan baru dibuat dengan terpisah sendiri trashBags.
  • Itu trashBags variabel lokal ke bagian dalam setiap dapur dan tidak dapat diakses di luar, tetapi fungsi bagian dalam getTrashBagproperti memang memiliki akses ke sana.
  • Setiap fungsi panggilan menciptakan penutupan, tetapi tidak perlu untuk menjaga penutupan di sekitar kecuali fungsi batin, yang memiliki akses ke bagian dalam penutupan, dapat dipanggil dari luar penutupan. Mengembalikan objek dengan getTrashBag berfungsi melakukan itu di sini.

691



Si Manusia Jerami

Saya perlu tahu berapa kali tombol telah diklik dan melakukan sesuatu pada setiap klik ketiga ...

Solusi Jelas Jelas

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Sekarang ini akan bekerja, tetapi itu melanggar batas luar dengan menambahkan variabel, yang tujuan utamanya adalah untuk melacak hitungan. Dalam beberapa situasi, ini akan lebih disukai karena aplikasi luar Anda mungkin memerlukan akses ke informasi ini. Namun dalam kasus ini, kami hanya mengubah setiap perilaku klik ketiga, jadi lebih baik melampirkan fungsi ini di dalam pengendali event.

Pertimbangkan opsi ini

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Perhatikan beberapa hal di sini.

Dalam contoh di atas, saya menggunakan perilaku penutupan JavaScript. Perilaku ini memungkinkan fungsi apa pun untuk memiliki akses ke ruang lingkup di mana ia diciptakan, tanpa batas. Untuk praktis menerapkan ini, saya segera memanggil fungsi yang mengembalikan fungsi lain, dan karena fungsi saya kembali memiliki akses ke variabel penghitungan internal (karena perilaku penutupan dijelaskan di atas) ini menghasilkan ruang lingkup pribadi untuk penggunaan oleh hasil fungsi ... Tidak begitu sederhana? Mari encerkan ...

Penutupan satu baris sederhana

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Semua variabel di luar fungsi yang dikembalikan tersedia untuk fungsi yang dikembalikan, tetapi tidak tersedia secara langsung ke objek fungsi yang dikembalikan ...

func();  // Alerts "val"
func.a;  // Undefined

Mendapatkan? Jadi dalam contoh utama kami, variabel hitungan terkandung dalam penutupan dan selalu tersedia untuk pengendali event, sehingga mempertahankan statusnya dari klik untuk mengklik.

Juga, status variabel privat ini sepenuhnya dapat diakses, untuk pembacaan dan penugasan ke variabel cakupan pribadi.

Ini dia; Anda sekarang sepenuhnya merangkum perilaku ini.

Posting Blog Lengkap (termasuk pertimbangan jQuery)


526



Penutupan sulit untuk dijelaskan karena mereka digunakan untuk membuat beberapa perilaku kerja yang setiap orang secara intuitif mengharapkan untuk bekerja. Saya menemukan cara terbaik untuk menjelaskannya (dan cara itu saya belajar apa yang mereka lakukan) adalah membayangkan situasi tanpa mereka:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Apa yang akan terjadi di sini jika JavaScript tidak tahu penutupan? Cukup ganti panggilan di baris terakhir dengan badan metodenya (yang pada dasarnya apa fungsi panggilan lakukan) dan Anda mendapatkan:

console.log(x + 3);

Sekarang, di mana definisi dari x? Kami tidak mendefinisikannya dalam lingkup saat ini. Satu-satunya solusi adalah membiarkannya plus5  membawa ruang lingkupnya (atau lebih tepatnya, ruang lingkup orang tuanya) di sekitar. Cara ini, x didefinisikan dengan baik dan terikat pada nilai 5.


444



Ini adalah upaya untuk menjernihkan beberapa kesalahpahaman yang mungkin terjadi tentang penutupan yang muncul di beberapa jawaban lainnya.

  • Penutupan tidak hanya dibuat ketika Anda mengembalikan fungsi batin. Bahkan, fungsi melampirkan tidak perlu kembali sama sekali agar penutupannya dibuat. Anda malah dapat menugaskan fungsi batin Anda ke variabel dalam lingkup luar, atau menyebarkannya sebagai argumen ke fungsi lain di mana ia bisa dipanggil segera atau kapan saja nanti. Oleh karena itu, penutupan fungsi melampirkan mungkin dibuat segera setelah fungsi melampirkan disebut karena setiap fungsi batin memiliki akses ke penutupan itu setiap kali fungsi bagian dalam dipanggil, sebelum atau sesudah fungsi melampirkan kembali.
  • Penutupan tidak merujuk salinan dari nilai-nilai lama variabel dalam ruang lingkupnya. Variabel itu sendiri adalah bagian dari penutupan, sehingga nilai yang terlihat ketika mengakses salah satu variabel tersebut adalah nilai terbaru pada saat diakses. Inilah sebabnya fungsi dalam yang dibuat di dalam loop dapat menjadi rumit, karena masing-masing memiliki akses ke variabel luar yang sama daripada mengambil salinan variabel pada saat fungsi dibuat atau dipanggil.
  • "Variabel" dalam penutupan mencakup semua fungsi bernama dideklarasikan dalam fungsi. Mereka juga termasuk argumen fungsi. Penutupan juga memiliki akses ke variabel yang mengandung penutupan, semua jalan sampai ke lingkup global.
  • Penutupan menggunakan memori, tetapi tidak menyebabkan kebocoran memori karena JavaScript dengan sendirinya membersihkan struktur lingkarannya sendiri yang tidak dirujuk. Kebocoran memori Internet Explorer yang melibatkan penutupan dibuat ketika gagal memutuskan nilai atribut DOM yang menutup referensi, sehingga mempertahankan referensi ke struktur yang mungkin melingkar.

342



OK, kipas penutup berusia 6 tahun. Apakah Anda ingin mendengar contoh penutupan yang paling sederhana?

Mari bayangkan situasi selanjutnya: seorang pengemudi duduk di dalam mobil. Mobil itu ada di dalam pesawat. Pesawat berada di bandara. Kemampuan pengemudi untuk mengakses barang-barang di luar mobilnya, tetapi di dalam pesawat, bahkan jika pesawat itu meninggalkan bandara, adalah penutupan. Itu dia. Saat Anda berusia 27 tahun, lihatlah penjelasan lebih rinci atau pada contoh di bawah ini.

Di sini adalah bagaimana saya dapat mengubah cerita pesawat saya ke dalam kode.

var plane = function (defaultAirport) {

    var lastAirportLeft = defaultAirport;

    var car = {
        driver: {
            startAccessPlaneInfo: function () {
                setInterval(function () {
                    console.log("Last airport was " + lastAirportLeft);
                }, 2000);
            }
        }
    };
    car.driver.startAccessPlaneInfo();

    return {
        leaveTheAirport: function (airPortName) {
            lastAirportLeft = airPortName;
        }
    }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

330