Halo, Habr!
Dalam
artikel sebelumnya, kami meneliti teori umum OOP sebagaimana diterapkan pada EcmaScript dan kekeliruan populer pengembang pemula mengenai perbedaan antara OOP di JS dan bahasa klasik.
Hari ini kita akan berbicara tentang dua konsep EcmaScript yang sama pentingnya, yaitu, hubungan entitas dengan konteks eksekusi (
ini adalah koneksi ini) dan hubungan entitas dengan konteks menghasilkan (
ScopeChain ).
Jadi mari kita mulai!
ini
Saat wawancara dalam menanggapi pertanyaan: "Ceritakan lebih banyak tentang
ini kepada kami ." Pengembang pemula, sebagai suatu peraturan, memberikan jawaban yang sangat samar: "
ini adalah objek" sebelum titik "yang digunakan untuk memanggil metode," "
ini adalah konteks di mana fungsi dipanggil," dll ...
Bahkan, situasi dengan konsep ini, yang merupakan pusat untuk EcmaScript, agak lebih rumit. Mari kita mencari tahu secara berurutan.
Katakanlah kita memiliki program JavaScript yang memiliki variabel yang dideklarasikan secara global; fungsi global; fungsi lokal (dideklarasikan di dalam fungsi lain), fungsi yang dikembalikan dari fungsi.
const a = 10; const b = 20; const x = { a: 15, b: 25, } function foo(){ return this.a + this.b; } function bar () { const a = 30; return a + b; } function fooBaz(){ function test () { return this.a + this.b; } return test(); } function fooBar() { const a = 40; const b = 50; return function () { return a + b; } } fooBar()();
Saat mentransfer kontrol ke kode yang dapat dieksekusi, sebuah entri dibuat ke dalam konteks eksekusi. Kode yang dapat dieksekusi - ini adalah kode apa pun yang kami jalankan pada waktu tertentu, dapat berupa kode global atau kode fungsi apa pun.
Konteks eksekusi adalah abstraksi yang melambangkan dan membatasi kode. Dari sudut pandang abstraksi ini, kode ini dibagi menjadi global (skrip yang terhubung, skrip inline) dan kode fungsi (kode fungsi yang disarangkan tidak termasuk dalam konteks fungsi induk).
Ada tipe ketiga - EvalCode. Dalam artikel ini, kami mengabaikannya.
Secara logis, himpunan konteks eksekusi adalah
tumpukan yang bekerja berdasarkan prinsip Last-in-First-out (lifo). Bagian bawah tumpukan selalu merupakan konteks global, dan bagian atas adalah yang dapat dieksekusi saat ini. Setiap kali suatu fungsi dipanggil, sebuah entri dibuat ke dalam konteksnya. Ketika suatu fungsi selesai, konteksnya berakhir. Konteks bekas dihapus dari tumpukan secara berurutan dan dalam urutan terbalik.
Lihatlah kode di atas. Kami memiliki panggilan ke fungsi
fooBar dalam kode global. Dalam fungsi
fooBar, kami mengembalikan
fungsi anonim yang segera kami panggil. Perubahan berikut terjadi dengan tumpukan:
konteks global masuk ke dalamnya - ketika
fooBar dipanggil
, konteksnya masuk ke tumpukan -
fooBar dihentikan, mengembalikan
fungsi anonim dan dihapus dari tumpukan -
fungsi anonim dipanggil, konteksnya masuk ke tumpukan -
fungsi anonim memenuhi, mengembalikan nilai dan konteksnya dihapus dari tumpukan - di akhir skrip,
konteks global dihapus dari tumpukan.
Konteks eksekusi dapat direpresentasikan secara kondisional sebagai objek. Salah satu properti dari objek ini adalah Lingkungan Lexikal (LO).
Lingkungan leksikal mengandung:
- semua deklarasi variabel konteks
- semua deklarasi fungsi
- semua parameter formal fungsi (jika kita berbicara tentang konteks fungsi)
Saat memasuki konteks eksekusi, penerjemah memindai konteks. Semua deklarasi variabel dan deklarasi fungsi naik ke awal konteks. Variabel dibuat sama dengan tidak terdefinisi, dan fungsi sepenuhnya siap untuk digunakan.
ini juga merupakan properti dari konteks eksekusi, tetapi bukan konteks itu sendiri, karena beberapa pewawancara pemula menjawab!
ini didefinisikan ketika memasuki konteks dan tetap tidak berubah sampai akhir masa konteks (sampai konteks dihapus dari tumpukan).
Dalam konteks eksekusi global,
ini ditentukan oleh
mode ketat : ketika mode ketat dimatikan, ini berisi objek global (di browser diproksikan ke tingkat atas di objek jendela), dengan 'gunakan ketat' ini tidak ditentukan.
ini dalam konteks fungsi - pertanyaannya jauh lebih menarik!
fungsi ini ditentukan oleh pemanggil dan tergantung pada sintaks panggilan. Misalnya, seperti yang kita ketahui, ada metode yang memungkinkan Anda untuk menyematkan ini saat menelepon (
panggilan ,
terapkan ) dan metode yang memungkinkan Anda membuat pembungkus dengan "memperbaiki ini" (
bind ). Dalam situasi ini, kami secara eksplisit menyatakan ini dan tidak ada keraguan tentang definisinya.
Dengan pemanggilan fungsi normal, situasinya jauh lebih rumit!
Salah satu tipe bawaan EcmaScript,
ReferenceType , akan membantu kami memahami bagaimana ini ditempelkan dalam fungsi. Ini adalah salah satu tipe internal yang tersedia di tingkat implementasi. Secara logis, ini adalah objek dengan dua
basis properti (referensi ke objek basis tertentu yang mengembalikan ReferenceType),
propertyName (representasi string dari pengidentifikasi objek untuk mana ReferenceType dikembalikan).
ReferenceType dikembalikan untuk semua deklarasi variabel, deklarasi fungsi, dan referensi properti (ini adalah kasus yang menarik bagi kami dari sudut pandang memahami hal ini).
Aturan untuk mendefinisikan
ini untuk fungsi yang dipanggil dengan cara biasa:
Jika ReferenceType berada di sebelah kiri kurung aktivasi fungsi, maka dasar ReferenceType ini dimasukkan ke dalam fungsi this
. Jika ada tipe lain di sebelah kiri tanda kurung, maka this
adalah objek global atau undefined
(sebenarnya null
, tetapi karena nol tidak memiliki nilai spesifik dari sudut pandang ecmascript, maka itu dilemparkan ke objek global, referensi yang mungkin sama dengan undefined
tergantung pada mode ketat).Mari kita lihat sebuah contoh:
const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();
Saya pikir metode definisi diilustrasikan dengan jelas. Sekarang pertimbangkan beberapa kasus yang kurang jelas.
Ekspresi Fungsional
Mari kita kembali ke ReferenceType kami sebentar. Tipe ini memiliki metode
GetValue bawaan yang mengembalikan tipe sebenarnya dari objek yang diterima melalui ReferenceType. Di zona ekspresi, GetValue selalu menyala.
Contoh:
(function (){ return this;
Ini disebabkan oleh fakta bahwa GetValue selalu memicu di zona ekspresi. GetValue mengembalikan tipe Fungsi, dan di sebelah kiri tanda kurasi aktivasi bukan ReferenceType. Ingat aturan kami untuk menentukan
ini :
Jika ada tipe lain di sebelah kiri tanda kurung, maka objek global dimasukkan ke dalam this
atau undefined
(sebenarnya null
, tetapi karena nol tidak memiliki nilai tertentu dari sudut pandang ecmascript, maka itu dikonversi ke objek global , tautan yang dapat sama dengan tidak terdefinisi tergantung pada mode ketat) .
Zona ekspresi adalah: penugasan (=), operator || atau operator logis lainnya, operator ternary, penginisialisasi array, daftar yang dipisahkan koma.
const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();
Situasi identik dalam ekspresi fungsional bernama. Bahkan dengan panggilan rekursif ke objek global ini atau
undefined
fungsi bersarang ini disebut di induk
Juga situasi yang penting!
const x = 0; function foo() { function bar(){ return this.x; } return bar(); } const obj = {x:10}; obj.test = foo; obj.test();
Ini karena panggilan ke
bar()
sama dengan panggilan ke
LE_foo.bar
, dan objek lingkungan leksikal tidak ditentukan seperti ini.
Fungsi konstruktor
Seperti yang saya tulis di atas:
fungsi ini ditentukan oleh pemanggil dan tergantung pada sintaks panggilan.
Kami memohon fungsi konstruktor menggunakan kata kunci baru. Keunikan metode aktivasi fungsi ini adalah metode fungsi internal
[[konstruk]] dipanggil, yang melakukan operasi tertentu (mekanisme untuk membuat entitas oleh desainer akan dibahas dalam artikel kedua atau ketiga pada OOP!) Dan memanggil metode internal
[[panggil]] , yang meletakkan dalam
hal ini dibuat fungsi konstruktor.
Rantai Lingkup
Rantai lingkup juga merupakan properti dari konteks eksekusi seperti ini. Ini adalah daftar objek lingkungan leksikal dari konteks saat ini dan semua konteks yang menghasilkan. Dalam rantai inilah pencarian variabel terjadi ketika menyelesaikan nama pengenal.
Catatan: ini mengaitkan fungsi dengan konteks eksekusi, dan ScopeChain dengan konteks anak.
Spesifikasi menyatakan bahwa ScopeChain adalah array:
SC = [LO, LO1, LO2,..., LOglobal];
Namun, dalam beberapa implementasi, seperti JS, rantai lingkup diimplementasikan melalui
daftar tertaut .
Untuk lebih memahami ScopeChain, kita akan membahas siklus hidup berbagai fungsi. Ini dibagi menjadi fase pembuatan dan fase eksekusi.
Ketika suatu fungsi dibuat, fungsi tersebut diberikan properti
[[RUANG LINGKUP]] internal.
Dalam
[[RUANG LINGKUP]] , rantai hirarki objek lingkungan leksikal dari konteks yang lebih tinggi (menghasilkan) dicatat. Properti ini tetap tidak berubah hingga fungsinya dihancurkan oleh pemulung.
Perhatikan!
[[RUANG LINGKUP]] , tidak seperti ScopeChain, adalah properti dari fungsi itu sendiri, bukan konteksnya.
Ketika suatu fungsi dipanggil, konteks pelaksanaannya diinisialisasi dan diisi. Konteksnya ditempelkan dengan ScopeChain = LO (dari fungsi itu sendiri) + [[RUANG LINGKUP]] (rantai hierarkis LO mempengaruhi konteks).
Resolusi nama pengidentifikasi - polling berurutan objek
LO dalam rantai
ScopeChain dari kiri ke kanan. Outputnya adalah ReferenceType yang properti dasarnya menunjuk ke objek LO di mana pengenal ditemukan, dan PropertyName akan menjadi representasi string dari nama pengenal.
Beginilah cara penutupan diatur di bawah tenda! Penutupan pada dasarnya adalah hasil dari pencarian di ScopeChain untuk semua variabel yang pengidentifikasinya ada dalam fungsi.
const x = 10; function foo () { return x; } (function (){ const x = 20; foo();
Contoh berikut menggambarkan siklus hidup
[[RUANG LINGKUP]] .
function foo () { const x = 10; const y = 20; return function () { return [x,y]; } } const x = 30; const bar = foo();
Pengecualian penting adalah
fungsi konstruktor . Untuk jenis fungsi ini, [[RUANG LINGKUP]] selalu menunjuk ke objek global.
Juga, jangan lupa bahwa jika salah satu tautan dalam rantai ScopeChain memiliki prototipe, maka pencarian akan dilakukan dalam prototipe juga.
Kesimpulan
Kami akan mengeluarkan ide-ide kunci secara tesis:
- ini adalah hubungan entitas dengan konteks eksekusi
- ScopeChain adalah hubungan entitas dengan semua konteks pemijahan
- this dan ScopeChain adalah properti konteks eksekusi
- fungsi ini ditentukan oleh pemanggil dan tergantung pada sintaks panggilan
- ScopeChain adalah lingkungan leksikal dari konteks saat ini + [[Cakupan]]
- [[Cakupan]] - ini adalah properti dari fungsi itu sendiri, berisi rantai hirarkis dari lingkungan leksikal dari menghasilkan konteks
Semoga artikel ini bermanfaat. Sampai artikel selanjutnya, teman-teman!