Halo, Habr!
Saya belum menulis apa pun untuk waktu yang lama, banyak pekerjaan di proyek selama beberapa minggu terakhir, tapi sekarang saya punya waktu luang, jadi saya memutuskan untuk memperkenalkan Anda sebuah artikel baru.
Hari ini kita akan terus menganalisis konsep-konsep kunci EcmaScript, berbicara tentang lingkungan Lexical dan Penutupan. Memahami konsep lingkungan Lexical sangat penting untuk memahami penutupan, dan penutupan adalah dasar dari begitu banyak teknik dan teknologi yang baik di dunia JS (yang didasarkan pada spesifikasi EcmaScript).
Jadi mari kita mulai.
Lingkungan leksikal (LexicalEnvironment, LO, LE)
Spesifikasi ES6 resmi mendefinisikan istilah ini sebagai:
Lexical Environment adalah jenis spesifikasi yang digunakan untuk menyelesaikan nama pengenal saat mencari variabel dan fungsi tertentu berdasarkan struktur leksikal dari ECMAScript code nesting. Lingkungan Leksikal terdiri dari catatan lingkungan dan, mungkin, referensi nol ke lingkungan Leksikal eksternal.
Mari kita lihat lebih dekat.
Saya akan membayangkan lingkungan leksikal sebagai semacam struktur yang menyimpan hubungan pengidentifikasi konteks dengan maknanya. Ini adalah semacam repositori variabel, fungsi, kelas yang dideklarasikan dalam lingkup konteks ini.
Secara teknis, LO adalah objek dengan dua properti:
- catatan lingkungan (ini adalah tempat semua iklan disimpan)
- tautan ke konteks generatif LO.
Melalui tautan ke konteks induk dari konteks saat ini, kami dapat, jika perlu, mendapatkan tautan ke "konteks kakek" dari konteks induk, dan seterusnya, ke konteks global, referensi ke induk yang akan menjadi nol. Dari definisi ini dapat disimpulkan bahwa lingkungan Leksikal adalah hubungan entitas dengan konteks asalnya. Semacam fungsi ScopeChain adalah analog dari lingkungan Lexical. Kami berbicara tentang ScopeChain secara rinci di
artikel ini .
let x = 10; let y = 20; const foo = z => { let x = 100; return x + y + z; } foo(30);
Secara teknis, proses penyelesaian nama pengidentifikasi akan terjadi seperti pada ScopeChain, yaitu polling berurutan objek dalam loop LO akan terjadi sampai pengidentifikasi yang diinginkan ditemukan. Jika pengidentifikasi tidak ditemukan, maka ReferenceError.
Lingkungan leksikal dibuat dan diisi pada tahap menciptakan konteks. Ketika konteks saat ini selesai dieksekusi, ia dihapus dari tumpukan panggilan, tetapi lingkungan Leksikalnya dapat terus hidup selama setidaknya ada satu tautan ke sana. Ini adalah salah satu kelebihan dari pendekatan modern terhadap desain bahasa pemrograman. Saya pikir ini layak untuk dibicarakan!
Stack Organization vs Memory yang Dibagikan Secara Dinamis
Dalam bahasa stack, variabel lokal disimpan di stack, yang diisi kembali ketika fungsi diaktifkan, ketika fungsi keluar, variabel lokalnya dihapus dari stack.
Dengan organisasi bertumpuk, tidak mungkin mengembalikan fungsi lokal dari suatu fungsi, atau memanggil fungsi ke variabel bebas.
Variabel bebas adalah variabel yang digunakan oleh suatu fungsi, tetapi itu bukan parameter formal atau variabel lokal untuk fungsi ini.
function testFn() { var locaVar = 10;
Dengan organisasi tumpukan, pencarian locaVar di LexicalEnvironment eksternal maupun kembalinya fungsi innerFn tidak dimungkinkan, karena innerFn juga merupakan deklarasi lokal untuk testFn. Setelah menyelesaikan testFn, semua variabel lokalnya hanya akan dihapus dari stack.
Oleh karena itu, konsep lain diusulkan - konsep memori yang dialokasikan secara dinamis (heap, heep) + pengumpul sampah + penghitungan referensi. Inti dari konsep ini sederhana: selama setidaknya ada satu referensi ke suatu objek, itu tidak dihapus dari memori. Rincian lebih lanjut dapat ditemukan di
sini .
Penutupan (Penutupan)
Penutupan adalah kombinasi dari blok kode dan data konteks di mana blok itu dihasilkan, yaitu ini adalah hubungan entitas dengan konteks penghasil melalui rantai LO atau SCopeChain.
Izinkan saya mengutip
artikel yang sangat bagus tentang hal ini:
function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter();
Ketika fungsi orang dieksekusi, JavaScript menciptakan konteks eksekusi baru dan lingkungan leksikal untuk fungsi tersebut. Setelah fungsi ini selesai, ini akan mengembalikan fungsi displayName dan ditugaskan ke peter variabel.
Dengan demikian, lingkungan leksikalnya akan terlihat seperti ini:
personLexicalEnvironment = { environmentRecord: { name : 'Peter', displayName: < displayName function reference> } outer: <globalLexicalEnvironment> }
Ketika fungsi orang selesai, konteks eksekusi muncul dari tumpukan. Tetapi lingkungan leksikalnya akan tetap tersimpan dalam memori, karena lingkungan leksikal dari fungsi internal DisplayName merujuk padanya. Dengan demikian, variabel-variabelnya masih akan tersedia dalam memori.
Ketika fungsi peter dieksekusi (yang sebenarnya merupakan referensi ke fungsi displayName), JavaScript menciptakan konteks eksekusi baru dan lingkungan leksikal untuk fungsi ini.
Jadi lingkungan leksikalnya akan terlihat seperti ini:
displayNameLexicalEnvironment = { environmentRecord: { } outer: <personLexicalEnvironment> }
Tidak ada variabel dalam fungsi displayName, catatan lingkungannya akan kosong. Selama eksekusi fungsi ini, JavaScript akan mencoba menemukan variabel nama di lingkungan leksikal fungsi.
Karena tidak ada variabel di lingkungan leksikal fungsi displayName, itu akan mencari di lingkungan leksikal eksternal, yaitu, lingkungan leksikal dari fungsi orang, yang masih dalam memori. JavaScript akan menemukan variabel ini dan nama dicetak ke konsol.
Karakteristik paling penting dari penutupan di ES adalah bahwa ia menggunakan lingkup statis (dalam sejumlah bahasa lain yang menggunakan penutupan, situasinya berbeda).
Contoh:
var a = 5; function testFn() { alert(a); } (function(funArg) { var a = 20; funArg();
Properti penutupan penting lainnya adalah situasi berikut:
var first; var second; function testFn() { var a = 10; first = function() { return ++a; } second = function() { return --a; } a = 2; first();
Yaitu kita melihat bahwa variabel bebas hadir dalam penutupan beberapa fungsi diubah oleh referensi oleh mereka.
Kesimpulan
Dalam kerangka kerja artikel ini, kami menjelaskan secara singkat dua konsep utama untuk EcmaScript: Lingkungan Lexical dan Penutupan. Faktanya, kedua topik ini jauh lebih luas. Jika komunitas ingin mendapatkan deskripsi yang lebih mendalam tentang perbedaan antara berbagai jenis lingkungan leksikal atau untuk mempelajari bagaimana v8 membangun penutupan, tulislah dalam komentar.