Apa yang Anda, penutupan dalam JavaScript?

Pada artikel ini saya akan mencoba menganalisis secara terperinci mekanisme untuk menerapkan penutupan dalam JavaScript. Untuk ini, saya akan menggunakan browser Chrome.

Mari kita mulai dengan definisi:
Penutupan adalah fungsi yang merujuk variabel independen (gratis). Dengan kata lain, fungsi yang didefinisikan dalam penutupan 'mengingat' lingkungan di mana ia dibuat.
MDN

Jika ada sesuatu yang tidak jelas bagi Anda dalam definisi ini, itu tidak menakutkan. Baca terus.

Saya sangat yakin bahwa memahami sesuatu lebih mudah dan lebih cepat dengan contoh-contoh spesifik.

Oleh karena itu, saya sarankan mengambil sepotong kode dan berjalan bersama penerjemah dari awal hingga akhir dalam langkah-langkah dan memilah apa yang terjadi.

Jadi mari kita mulai:


Gambar 1

Kami berada dalam konteks panggilan global, Global (alias Window di browser) dan kami melihat bahwa fungsi utama sudah ada dalam konteks saat ini dan siap untuk bekerja.


Gambar 2

Ini terjadi karena semua Deklarasi Fungsi (selanjutnya disebut FD) selalu naik dalam konteks apa pun, segera diinisialisasi dan siap untuk bekerja. Hal yang sama terjadi dengan variabel yang dideklarasikan melalui var, hanya nilainya yang diinisialisasi sebagai tidak terdefinisi.

Penting juga dipahami bahwa JavaScript juga "memunculkan" variabel-variabel yang dideklarasikan melalui let dan const. Satu-satunya perbedaan adalah bahwa itu tidak menginisialisasi mereka sebagai var atau FD. Oleh karena itu, ketika kami mencoba mengaksesnya sebelum inisialisasi, kami mendapatkan Kesalahan Referensi.

Juga, pada intinya kita melihat properti yang disembunyikan secara internal [[Cakupan]] - ini adalah daftar konteks eksternal di mana main memiliki akses. Dalam kasus kami, Global ada di sana, karena main diluncurkan dalam konteks global.

Fakta bahwa dalam JavaScript inisialisasi referensi ke lingkungan eksternal terjadi pada saat fungsi dibuat, dan bukan pada saat eksekusi, menunjukkan bahwa JS adalah bahasa dengan cakupan statis. Dan itu penting.

Silakan:


Gambar 3

Kami pergi ke fungsi utama dan hal pertama yang menarik perhatian Anda adalah objek Lokal (dalam spesifikasi - localEnv). Di sana kita melihat a , karena variabel ini dideklarasikan melalui var dan itu 'muncul', yah, dan menurut tradisi kita melihat ketiga FD (foo, bar, baz). Sekarang mari kita cari tahu dari mana semua itu berasal.

Ketika konteks apa pun dimulai, operasi abstrak NewDeclarativeEnvironment diluncurkan , yang memungkinkan Anda untuk menginisialisasi LexicalEnvironment (selanjutnya LE) dan VariableEnvironment . Juga, NewDeclarativeEnvironment mengambil 1 argumen - LE eksternal, untuk membuat [[Cakupan]] yang kita bicarakan di atas. LE adalah API yang memungkinkan kita untuk menentukan hubungan antara pengidentifikasi dan variabel individual, fungsi. LE terdiri dari 2 komponen:

  1. Catatan Lingkungan - catatan lingkungan yang memungkinkan Anda untuk menentukan hubungan antara pengidentifikasi dan apa yang tersedia bagi kami dalam konteks panggilan saat ini
  2. Tautan ke LE eksternal. Setiap fungsi memiliki properti [[Cakupan]] internal ketika dibuat .

VariableEnvironment - paling sering sama dengan LE. Perbedaan antara keduanya adalah bahwa nilai VariableEnvironment tidak pernah berubah, dan LE dapat berubah selama eksekusi kode. Untuk menyederhanakan pemahaman lebih lanjut, saya mengusulkan untuk menggabungkan komponen ini menjadi satu - LE.

Juga di Local saat ini ada ini karena fakta bahwa ThisBinding disebut - ini juga merupakan metode abstrak yang menginisialisasi ini dalam konteks saat ini.

Tentu saja, setiap FD segera menerima [[Cakupan]]:


Gambar 4

Kita melihat bahwa semua FD yang diterima di [[Cakupan]] array [Penutupan utama, Global], yang logis.

Juga pada gambar yang kita lihat Call Stack - ini adalah struktur data yang bekerja berdasarkan prinsip LIFO - bertahan lebih dulu. Karena JavaScript adalah utas tunggal, hanya satu konteks yang dapat dieksekusi pada satu waktu. Dalam kasus kami, ini adalah konteks fungsi utama. Setiap panggilan fungsi baru menciptakan konteks baru, yang ditumpuk.

Di atas tumpukan selalu menjadi konteks eksekusi saat ini. Setelah fungsi selesai dieksekusi dan penerjemah keluar, konteks panggilan dihapus dari tumpukan. Itu saja yang perlu kita ketahui tentang Call Stack di artikel ini :)

Kami merangkum apa yang terjadi dalam konteks saat ini:

  • Pada saat pembuatan, utama diterima [[Cakupan]] dengan tautan ke lingkungan eksternal
  • Juru bahasa memasuki tubuh fungsi utama
  • Call Stack menjadikan konteks eksekusi sebagai utama
  • Ini diinisialisasi
  • Inisialisasi LE

Bahkan, bagian tersulit sudah usai. Kami melanjutkan ke langkah selanjutnya dalam kode:

Sekarang kita perlu memanggil baz untuk mendapatkan hasilnya.


Gambar 5

Konteks panggilan baz baru telah ditambahkan ke Call Stack. Kita melihat bahwa objek Penutupan baru telah muncul. Di sini kita mendapatkan apa yang tersedia bagi kita dari [[Cakupan]]. Jadi kita sampai pada intinya. Ini adalah penutupannya. Seperti yang Anda lihat pada Gambar 4, Penutupan (utama) masuk pertama dalam daftar konteks 'cadangan' di baz. Sekali lagi tidak ada sihir.

Mari kita panggil foo:


Gambar 6

Penting untuk mengetahui bahwa di mana pun kami memanggil foo, itu akan selalu mengikuti pengidentifikasi yang tidak ditentukan dalam rantai [[Cakupan]] -nya. Yaitu, di main dan kemudian di Global, jika tidak ditemukan di main.

Setelah mengeksekusi foo, dia mengembalikan nilainya, dan konteksnya melompat keluar dari Call Stack.
Kami meneruskan panggilan ke fungsi bilah. Dalam konteks eksekusi bar, ada variabel dengan nama yang sama dengan variabel di LE foo - a . Tapi, seperti yang sudah Anda tebak, ini tidak memengaruhi apa pun. foo masih akan mengambil nilai dari [[Cakupan]] nya.
Tempat panggilan tidak memengaruhi Lingkup, hanya tempat penciptaan
logachyova


Gambar 7

Akibatnya, baz akan mengembalikan 300 dan akan dikeluarkan dari Call Stack. Kemudian hal yang sama akan terjadi dengan konteks utama, fragmen kode kita akan selesai dieksekusi.

Kami merangkum:

  • Selama pembuatan fungsi, [[Cakupan]] diatur . Ini sangat penting untuk memahami penutupan, karena penerjemah segera mengikuti tautan ini ketika mencari nilai
  • Kemudian, ketika fungsi ini dipanggil, konteks eksekusi aktif dibuat, yang ditempatkan di Call Stack
  • Penjilidan ini dijalankan dan ini diatur untuk konteks saat ini
  • LE diinisialisasi, dan semua argumen fungsi, variabel dideklarasikan melalui var dan FD menjadi tersedia. Selanjutnya, jika ada variabel yang dideklarasikan melalui let atau const, mereka juga ditambahkan ke LE
  • Jika penerjemah tidak menemukan pengidentifikasi dalam konteks saat ini, maka [[Cakupan]] digunakan untuk pencarian lebih lanjut, yang diurutkan semua pada gilirannya. Jika nilainya ditemukan, maka tautannya jatuh ke objek Penutupan khusus. Pada saat yang sama, untuk setiap konteks yang ditutup saat ini, Penutupan terpisah dibuat dengan variabel yang diperlukan
  • Jika nilai tidak ditemukan dalam Lingkup apa pun, termasuk Global, ReferenceError dikembalikan.

Itu saja!

Saya harap artikel ini bermanfaat bagi Anda dan sekarang Anda mengerti bagaimana mekanisme penguncian dalam JavaScript bekerja.

Sampai jumpa :) Dan sampai jumpa. Suka dan berlangganan ke saluran saya :)

Source: https://habr.com/ru/post/id459166/


All Articles