Konteks eksekusi JavaScript dan tumpukan panggilan

Jika Anda seorang pengembang JavaScript atau ingin menjadi pengembang JavaScript, ini berarti Anda perlu memahami mekanisme internal untuk mengeksekusi kode JS. Secara khusus, pemahaman tentang apa konteks eksekusi dan tumpukan panggilan, mutlak diperlukan untuk menguasai konsep JavaScript lainnya, seperti meningkatkan variabel, cakupan, dan penutupan. Bahan, terjemahan yang kami terbitkan hari ini, dikhususkan untuk konteks eksekusi dan tumpukan panggilan dalam JavaScript.



Konteks eksekusi


Konteks eksekusi adalah, dalam istilah yang disederhanakan, sebuah konsep yang menggambarkan lingkungan di mana kode JavaScript dieksekusi. Kode selalu dijalankan di dalam konteks.

▍ Jalankan tipe konteks


JavaScript memiliki tiga jenis konteks eksekusi:

  • Konteks eksekusi global. Ini adalah konteks eksekusi standar dasar. Jika beberapa kode tidak ada di dalam fungsi apa pun, maka kode ini milik konteks global. Konteks global dicirikan oleh keberadaan objek global, yang, dalam kasus browser, adalah objek window , dan fakta bahwa this menunjuk ke objek global ini. Suatu program hanya dapat memiliki satu konteks global.
  • Konteks pelaksanaan fungsi. Setiap kali fungsi dipanggil, konteks baru dibuat untuk itu. Setiap fungsi memiliki konteks eksekusi sendiri. Suatu program dapat secara bersamaan memiliki banyak konteks untuk menjalankan fungsi. Saat membuat konteks baru untuk eksekusi suatu fungsi, ia akan melalui urutan langkah-langkah tertentu, yang akan kita bahas di bawah ini.
  • Konteks eksekusi dari fungsi eval . Kode yang dieksekusi di dalam fungsi eval juga memiliki konteks eksekusi sendiri. Namun, fungsi eval jarang digunakan, jadi di sini kita tidak akan berbicara tentang konteks eksekusi ini.

Tumpukan eksekusi


Tumpukan eksekusi, yang juga disebut tumpukan panggilan, adalah tumpukan LIFO yang digunakan untuk menyimpan konteks eksekusi yang dibuat selama eksekusi kode.

Ketika mesin JS mulai memproses skrip, mesin membuat konteks eksekusi global dan menempatkannya di tumpukan saat ini. Ketika suatu perintah untuk memanggil suatu fungsi terdeteksi, mesin menciptakan konteks eksekusi baru untuk fungsi ini dan menempatkannya di atas tumpukan.

Mesin melakukan fungsi yang konteks eksekusi berada di bagian atas tumpukan. Ketika fungsi selesai, konteksnya dihapus dari tumpukan dan kontrol ditransfer ke konteks yang ada di elemen tumpukan sebelumnya.

Kami akan mengeksplorasi ide ini dengan contoh berikut:

 let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context'); 

Begini cara tumpukan panggilan akan berubah ketika kode ini dijalankan.


Status Stack Panggilan

Ketika kode di atas dimuat ke browser, mesin JavaScript membuat konteks eksekusi global dan menempatkannya di tumpukan panggilan saat ini. Saat melakukan panggilan ke fungsi first() , mesin membuat konteks baru untuk fungsi ini dan menempatkannya di bagian atas tumpukan.

Ketika fungsi second() dipanggil dari fungsi first() , konteks eksekusi baru dibuat untuk fungsi ini dan juga didorong ke stack. Setelah fungsi second() menyelesaikan pekerjaannya, konteksnya dihapus dari tumpukan dan kontrol ditransfer ke konteks eksekusi yang terletak di tumpukan di bawahnya, yaitu, ke konteks fungsi first() .

Ketika fungsi first() keluar, konteksnya muncul dari tumpukan dan kontrol ditransfer ke konteks global. Setelah semua kode dieksekusi, mesin mengambil konteks eksekusi global dari tumpukan saat ini.

Tentang membuat konteks dan menjalankan kode


Sejauh ini, kami berbicara tentang bagaimana mesin JS mengelola konteks eksekusi. Sekarang mari kita bicara tentang bagaimana konteks eksekusi dibuat, dan apa yang terjadi padanya setelah mereka dibuat. Secara khusus, kita berbicara tentang tahap menciptakan konteks eksekusi dan tahap eksekusi kode.

▍ Tahap menciptakan konteks eksekusi


Sebelum kode JavaScript dieksekusi, konteks eksekusi dibuat. Dalam proses penciptaannya, tiga tindakan dilakukan:

  1. Nilai ini ditentukan dan this (ini mengikat) terikat.
  2. Komponen LexicalEnvironment dibuat.
  3. Komponen VariableEnvironment dibuat.

Secara konseptual, konteks eksekusi dapat direpresentasikan sebagai berikut:

 ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, } 

Ini mengikat


Dalam konteks eksekusi global, this berisi referensi ke objek global (seperti yang telah disebutkan, di browser itu adalah objek window ).

Dalam konteks eksekusi fungsi, nilai this tergantung pada bagaimana fungsi dipanggil. Jika ini disebut sebagai metode objek, maka nilai this terikat ke objek ini. Dalam kasus lain, this terikat ke objek global atau diatur ke undefined (dalam mode ketat). Pertimbangkan sebuah contoh:

 let foo = { baz: function() { console.log(this); } } foo.baz();    // 'this'    'foo',    'baz'              //    'foo' let bar = foo.baz; bar();       // 'this'     window,                 //      

Lingkungan leksikal


Menurut spesifikasi ES6, Lingkungan Leksikal adalah istilah yang digunakan untuk mendefinisikan hubungan antara pengidentifikasi dan variabel individu dan fungsi berdasarkan pada struktur lexical nesting dari kode skrip ECMAS. Lingkungan leksikal terdiri dari Catatan Lingkungan dan referensi ke lingkungan leksikal eksternal, yang dapat menjadi null .

Sederhananya, lingkungan leksikal adalah struktur yang menyimpan informasi tentang korespondensi pengidentifikasi dan variabel. Di sini, dengan "pengidentifikasi" berarti nama variabel atau fungsi, dan "variabel" adalah referensi ke objek tertentu (termasuk fungsi) atau nilai primitif.

Dalam lingkungan leksikal ada dua komponen:

  1. Catatan lingkungan. Di sinilah deklarasi variabel dan fungsi disimpan.
  2. Tautan ke lingkungan eksternal. Kehadiran tautan semacam itu menunjukkan bahwa lingkungan leksikal memiliki akses ke lingkungan leksikal induk (ruang lingkup).

Ada dua jenis lingkungan leksikal:

  1. Lingkungan global (atau konteks eksekusi global) adalah lingkungan leksikal yang tidak memiliki lingkungan eksternal. Referensi lingkungan global ke lingkungan eksternal adalah null . Di lingkungan global (dalam catatan lingkungan), entitas bahasa bawaan (seperti Object , Array , dan sebagainya) tersedia yang terkait dengan objek global, ada juga variabel global yang ditentukan oleh pengguna. Nilai this dalam lingkungan ini menunjuk ke objek global.
  2. Lingkungan fungsi di mana, dalam catatan lingkungan, variabel yang dideklarasikan oleh pengguna disimpan. Referensi ke lingkungan eksternal dapat menunjukkan objek global dan fungsi eksternal untuk fungsi yang dimaksud.

Ada dua jenis catatan lingkungan:

  1. Catatan lingkungan deklaratif yang menyimpan variabel, fungsi, dan parameter.
  2. Catatan objek lingkungan yang digunakan untuk menyimpan informasi tentang variabel dan fungsi dalam konteks global.

Akibatnya, dalam lingkungan global, rekaman lingkungan diwakili oleh catatan lingkungan objek, dan dalam lingkungan fungsi, oleh catatan lingkungan deklaratif.

Perhatikan bahwa dalam lingkungan fungsi, catatan deklaratif lingkungan juga berisi objek arguments , yang menyimpan korespondensi antara indeks dan nilai argumen yang diteruskan ke fungsi, dan informasi tentang jumlah argumen tersebut.

Lingkungan leksikal dapat direpresentasikan sebagai pseudocode berikut:

 GlobalExectionContext = { LexicalEnvironment: {   EnvironmentRecord: {     Type: "Object",     //        }   outer: <null> } } FunctionExectionContext = { LexicalEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //        }   outer: <        > } } 

Variabel lingkungan


Lingkungan Variabel juga merupakan lingkungan leksikal yang rekaman lingkungannya menyimpan binding yang dibuat menggunakan perintah VariableStatement dalam konteks eksekusi saat ini.

Karena lingkungan variabel juga merupakan lingkungan leksikal, ia memiliki semua properti lingkungan leksikal yang dijelaskan di atas.

Dalam ES6, ada satu perbedaan antara komponen LexicalEnvironment dan VariableEnvironment . Terdiri dari fakta bahwa yang pertama digunakan untuk menyimpan deklarasi fungsi dan variabel yang dideklarasikan menggunakan kata kunci let dan const , dan yang terakhir hanya digunakan untuk menyimpan binding variabel yang dideklarasikan menggunakan kata kunci var .

Pertimbangkan contoh-contoh yang menggambarkan apa yang baru saja kita diskusikan:

 let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30); 

Representasi skematis dari konteks eksekusi untuk kode ini akan terlihat seperti ini:

 GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {   EnvironmentRecord: {     Type: "Object",     //          a: < uninitialized >,     b: < uninitialized >,     multiply: < func >   }   outer: <null> }, VariableEnvironment: {   EnvironmentRecord: {     Type: "Object",     //          c: undefined,   }   outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //          Arguments: {0: 20, 1: 30, length: 2},   },   outer: <GlobalLexicalEnvironment> }, VariableEnvironment: {   EnvironmentRecord: {     Type: "Declarative",     //          g: undefined   },   outer: <GlobalLexicalEnvironment> } } 

Seperti yang mungkin Anda perhatikan, variabel dan konstanta yang dideklarasikan menggunakan kata kunci let dan const tidak memiliki nilai yang terkait, dan variabel yang dideklarasikan menggunakan kata kunci var diatur ke undefined .

Ini karena selama pembuatan konteks, kode mencari deklarasi variabel dan fungsi, sedangkan deklarasi fungsi disimpan sepenuhnya di lingkungan. Nilai-nilai variabel, ketika menggunakan var , diatur ke undefined , dan ketika menggunakan let atau const tetap tidak diinisialisasi.

Itulah sebabnya Anda dapat mengakses variabel yang dideklarasikan dengan var sebelum dideklarasikan (walaupun mereka undefined akan undefined ), tetapi ketika Anda mencoba mengakses variabel atau konstanta yang dideklarasikan dengan let dan const dieksekusi sebelum mereka dideklarasikan, kesalahan .

Apa yang baru saja kami jelaskan disebut "variabel pengangkat". Deklarasi variabel β€œnaik” ke atas lingkup leksikal mereka sebelum melakukan operasi untuk memberikan nilai apa pun kepada mereka.

▍ Tahap pelaksanaan kode


Ini mungkin bagian paling sederhana dari materi ini. Pada tahap ini, nilai diberikan ke variabel dan kode dieksekusi.

Harap dicatat bahwa jika, selama eksekusi kode, mesin JS tidak dapat menemukan nilai variabel yang dideklarasikan menggunakan kata kunci let di tempat deklarasi, itu akan memberikan variabel ini nilai yang undefined .

Ringkasan


Kami baru saja membahas mekanisme internal untuk mengeksekusi kode JavaScript. Meskipun untuk menjadi pengembang JS yang sangat baik, tidak perlu mengetahui semua ini, jika Anda memiliki pemahaman tentang konsep-konsep di atas, itu akan membantu Anda lebih baik dan lebih dalam memahami mekanisme bahasa lainnya, seperti meningkatkan variabel, ruang lingkup, sirkuit pendek.

Pembaca yang budiman! Apa lagi yang menurut Anda selain konteks eksekusi dan tumpukan panggilan yang berguna untuk diketahui oleh pengembang JavaScript?

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


All Articles