Saat Anda menggunakan objek, variabel, atau fungsi, Anda melakukannya dengan sengaja. Anda berpikir, "Di sinilah saya membutuhkan variabel," dan Anda menambahkannya ke kode Anda. Penutupan, bagaimanapun, adalah sesuatu yang lain. Sementara sebagian besar programmer mulai belajar tentang closure, orang-orang ini sudah menggunakan closure tanpa menyadarinya. Mungkin hal yang sama terjadi pada Anda. Jadi mempelajari penutupan bukanlah tentang mempelajari ide baru daripada belajar bagaimana mengenali sesuatu yang Anda temui berkali-kali sebelumnya. Singkatnya, closure adalah saat suatu fungsi mengakses variabel yang dideklarasikan di luarnya. Misalnya, closure terdapat dalam potongan kode ini:
let users = ['Alice', 'Dan', 'Jessica'];
let query = 'A';
let user = users.filter(user => user.startsWith(query));
Perhatikan bahwa ini
user => user.startsWith(query)adalah sebuah fungsi. Dia menggunakan variabel query. Dan variabel ini dideklarasikan di luar fungsi. Ini adalah penutupan.
Anda dapat melewati membaca jika Anda suka. Sisa bahan ini melihat penutupan dalam cahaya yang berbeda. Alih-alih berbicara tentang apa itu closure, bagian artikel ini akan membahas detail pendeteksian closure. Ini mirip dengan cara kerja programmer pertama di tahun 1960-an.
Langkah 1: Fungsi dapat mengakses variabel yang dideklarasikan di luarnya
Untuk memahami closure, Anda harus cukup familiar dengan variabel dan fungsi. Dalam contoh ini, kami mendeklarasikan variabel
fooddi dalam fungsi eat:
function eat() {
let food = 'cheese';
console.log(food + ' is good');
}
eat(); // 'cheese is good'
Bagaimana jika Anda ingin mengubah nilai variabel nanti
food, di luar fungsi eat? Untuk melakukan ini, kita dapat menghapus variabel itu sendiri dari fungsi fooddan memindahkannya ke level yang lebih tinggi:
let food = 'cheese'; //
function eat() {
console.log(food + ' is good');
}
Ini memungkinkan Anda untuk mengubah variabel
food"dari luar" bila diperlukan:
eat(); // 'cheese is good'
food = 'pizza';
eat(); // 'pizza is good'
food = 'sushi';
eat(); // 'sushi is good'
Dengan kata lain, variabel
foodtidak lagi eatlokal ke fungsi . Tetapi fungsinya eat, terlepas dari ini, tidak memiliki masalah saat bekerja dengan variabel ini. Fungsi dapat mengakses variabel yang dideklarasikan di luarnya. Berhenti sejenak dan periksa diri Anda sendiri, pastikan Anda tidak memiliki masalah dengan ide ini. Setelah pikiran ini tertanam kuat di benak Anda, lanjutkan ke langkah kedua.
Langkah 2: Menempatkan kode dalam pemanggilan fungsi
Katakanlah kita memiliki beberapa kode:
/* */
Tidak masalah kode apa itu. Tapi katakanlah kita perlu menjalankannya dua kali.
Cara pertama untuk melakukannya adalah dengan membuat salinan kode:
/* */
/* */
Cara lain adalah dengan meletakkan kode dalam satu lingkaran:
for (let i = 0; i < 2; i++) {
/* */
}
Dan cara ketiga, yang sangat menarik bagi kita saat ini, adalah meletakkan kode ini dalam sebuah fungsi:
function doTheThing() {
/* */
}
doTheThing();
doTheThing();
Menggunakan fungsi memberi kita fleksibilitas maksimum, karena memungkinkan kita memanggil kode yang diberikan kapan saja, kapan saja, dan dari mana saja dalam program.
Faktanya, jika perlu, kita dapat membatasi diri kita hanya pada satu panggilan dari fungsi baru:
function doTheThing() {
/* */
}
doTheThing();
Harap perhatikan bahwa kode di atas sama dengan cuplikan kode asli:
/* */
Dengan kata lain, jika kita mengambil beberapa bagian kode dan "membungkusnya" dalam sebuah fungsi, dan kemudian memanggil fungsi ini tepat sekali, maka kita tidak akan mempengaruhi apa pun yang dilakukan kode ini. Ada beberapa pengecualian untuk aturan ini, yang tidak akan kami perhatikan, tetapi secara umum, kami dapat berasumsi bahwa aturan ini benar. Pikirkan sejenak, biasakan ide ini.
Langkah 3: Mendeteksi penutupan
Kami menemukan dua gagasan:
- Fungsi dapat bekerja dengan variabel yang dideklarasikan di luarnya.
- Jika Anda menempatkan kode di suatu fungsi dan memanggil fungsi ini satu kali, itu tidak akan memengaruhi hasil kode.
Sekarang mari kita bicara tentang apa yang akan terjadi jika kedua gagasan ini digabungkan.
Mari kita ambil kode contoh yang kita lihat di langkah pertama:
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
Sekarang mari kita letakkan seluruh contoh ini dalam sebuah fungsi yang akan kita panggil hanya sekali:
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
}
liveADay();
Baca kedua contoh kode sebelumnya dan pastikan keduanya setara.
Contoh kedua berhasil! Tapi mari kita lihat lebih dekat. Perhatikan bahwa fungsinya
eatada di dalam fungsi liveADay. Apakah ini diperbolehkan dalam JavaScript? Apakah benar-benar mungkin untuk menggabungkan satu fungsi ke dalam fungsi lainnya?
Ada bahasa di mana kode yang disusun dengan cara ini akan menjadi salah. Misalnya, di C, kode seperti itu salah (tidak ada penutupan dalam bahasa ini). Ini berarti bahwa saat menggunakan C, kesimpulan kedua kita akan salah - Anda tidak bisa begitu saja mengambil potongan kode sembarangan dan "membungkusnya" dalam sebuah fungsi. Tetapi tidak ada batasan seperti itu dalam JavaScript.
Mari kita pikirkan tentang kode ini lagi, dengan memberi perhatian khusus di mana variabel dideklarasikan dan di mana ia digunakan.
food:
function liveADay() {
let food = 'cheese'; // `food`
function eat() {
console.log(food + ' is good'); // `food`
}
eat();
}
liveADay();
Mari kita bahas kode ini selangkah demi selangkah bersama. Pertama, kami mendeklarasikan, di tingkat atas, sebuah fungsi
liveADay. Kami segera meneleponnya. Fungsi ini memiliki variabel lokal food. Fungsi tersebut juga dideklarasikan di dalamnya eat. Kemudian liveADayfungsinya dipanggil secara internal eat. Karena fungsi tersebut eatberada di dalam suatu fungsi liveADay, ia eat"melihat" semua variabel yang dideklarasikan liveADay. Inilah mengapa fungsi eatdapat membaca nilai variabel food.
Ini disebut penutupan.
Kita berbicara tentang keberadaan sebuah closure ketika sebuah fungsi (seperti
eat) membaca atau menulis nilai sebuah variabel (seperti food), yang dideklarasikan di luarnya (misalnya, dalam sebuah fungsi liveADay).
Pikirkan tentang kata-kata ini, baca ulang. Uji diri Anda dengan menemukan apa yang sedang kita bicarakan di kode sampel kami.
Berikut adalah contoh yang diberikan di awal artikel:
let users = ['Alice', 'Dan', 'Jessica'];
let query = 'A';
let user = users.filter(user => user.startsWith(query));
Mungkin lebih mudah untuk melihat closure dengan menulis ulang contoh ini menggunakan ekspresi fungsi:
let users = ['Alice', 'Dan', 'Jessica'];
// 1. query
let query = 'A';
let user = users.filter(function(user) {
// 2.
// 3. query ( !)
return user.startsWith(query);
});
Ketika sebuah fungsi mengakses variabel yang dideklarasikan di luarnya, kami menyebutnya closure. Istilah itu sendiri digunakan dengan cukup longgar. Beberapa orang akan memanggil fungsi bertingkat itu sendiri, seperti yang ditunjukkan pada contoh, "closure". Orang lain mungkin merujuk ke pengakses variabel eksternal dengan menyebutnya sebagai "penutupan". Dalam praktiknya, ini tidak masalah.
Fungsi memanggil hantu
Penutupan mungkin tampak seperti konsep sederhana yang menipu bagi Anda sekarang. Tetapi ini tidak berarti bahwa mereka kekurangan beberapa fitur yang tidak jelas. Jika Anda memikirkan dengan cermat fakta bahwa suatu fungsi dapat membaca dan menulis nilai variabel di luarnya, ternyata hal ini memiliki konsekuensi yang cukup serius.
Misalnya, ini berarti variabel seperti itu akan "aktif" selama fungsi yang bersarang dalam fungsi lain dapat dipanggil.
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
// eat
setTimeout(eat, 5000);
}
liveADay();
Dalam contoh ini, ini
foodadalah variabel lokal di dalam pemanggilan fungsi liveADay(). Saya hanya ingin memutuskan bahwa variabel ini akan "menghilang" setelah keluar dari fungsi, dan tidak akan kembali menghantui kita seperti hantu.
Namun dalam fungsinya,
liveADaykami meminta browser untuk memanggil fungsi tersebut eatsetelah lima detik. Dan fungsi ini membaca nilai variabel food. Akibatnya, ternyata mesin JavaScript perlu menjaga variabel yang foodterkait dengan panggilan ini tetap hidup liveADay()hingga fungsi tersebut dipanggil eat.
Dalam pengertian ini, closure dapat dilihat sebagai "hantu" dari panggilan fungsi sebelumnya, atau sebagai "kenangan" dari panggilan tersebut. Padahal menjalankan fungsinya
liveADay()telah lama berakhir, variabel yang dideklarasikan di dalamnya harus tetap ada selama fungsi bersarang eatdapat dipanggil. Untungnya, JavaScript menangani mekanisme ini, jadi kami tidak perlu melakukan sesuatu yang khusus dalam situasi ini.
Mengapa "penutupan" disebut seperti itu?
Anda mungkin bertanya-tanya mengapa penutupan disebut seperti itu. Alasannya terutama karena sejarah. Siapa pun yang akrab dengan jargon komputer mungkin mengatakan bahwa ekspresi seperti ini
user => user.startsWith(query)memiliki "penjilidan terbuka". Dengan kata lain, dari ekspresi ini jelas apa itu user(parameter), tetapi jika dilihat secara terpisah, tidak jelas apa itu query. Ketika kita mengatakan bahwa itu sebenarnya queryadalah variabel yang dideklarasikan di luar fungsi, kita "menutup" pengikatan terbuka itu. Dengan kata lain, kami mendapatkan penutupan.
Penutupan tidak diterapkan di semua bahasa pemrograman. Misalnya, dalam beberapa bahasa, seperti C, Anda tidak dapat menggunakan fungsi bertingkat sama sekali. Akibatnya, fungsi tersebut hanya dapat bekerja dengan variabel lokalnya atau dengan variabel global. Namun, tidak pernah ada situasi di mana ia dapat mengakses variabel lokal dari fungsi induk. Ini sebenarnya adalah batasan yang sangat tidak menyenangkan.
Ada juga bahasa seperti Rust yang mengimplementasikan closure. Tetapi mereka menggunakan sintaks yang berbeda untuk mendeskripsikan closure dan fungsi normal. Akibatnya, jika Anda perlu membaca nilai variabel di luar fungsi, Anda perlu menggunakan konstruksi khusus menggunakan Rust. Alasan untuk ini adalah bahwa penggunaan closure mungkin memerlukan mekanisme internal bahasa untuk menyimpan variabel eksternal (disebut "lingkungan") bahkan setelah pemanggilan fungsi selesai. Beban tambahan pada sistem ini dapat diterima di JavaScript, tetapi dapat menyebabkan masalah kinerja saat digunakan dalam bahasa tingkat rendah.
Sekarang, saya harap Anda memahami konsep closure di JavaScript.
Apakah Anda kesulitan memahami beberapa konsep JavaScript?
