宣告
本系列文章內容全部梳理自以下幾個來源:
- 《JavaScript權威指南》
- MDN web docs
- Github:smyhvae/web
- Github:goddyZhao/Translation/JavaScript
作為一個前端小白,入門跟著這幾個來源學習,感謝作者的分享,在其基礎上,通過自己的理解,梳理出的知識點,或許有遺漏,或許有些理解是錯誤的,如有發現,歡迎指點下。
PS:梳理的內容以《JavaScript權威指南》這本書中的內容為主,因此接下去跟 JavaScript 語法相關的系列文章基本只介紹 ES5 標準規範的內容、ES6 等這系列梳理完再單獨來講講。
正文-執行上下文EC和變數物件VO
EC:Execution Context,中文翻譯執行上下文,也有翻譯成執行環境的。
VO:Variable object,中文翻譯變數物件。
這兩個概念很重要,涉及到作用域以及作用域鏈的原理。
執行上下文 EC
先說說Android中的上下文:
- Android
Android 中也有上下文:Context,四大元件都是上下文,還有一個全域性的 Application上下文。在 Android 中基本是以四大元件為界限,每建立一個四大元件,都會產生一個上下文,比如每個 Activity 都是獨立的上下文。
在 Android 中,上下文 Context 的作用大體上用於標識各種資源的所屬,要載入一張圖片、建立一個 View、彈一個 Dialog 等等,你需要告訴系統,這些是誰發出的指令,要掛載到哪個上下文,這些資源依賴於上下文的生命週期。
所以才會出現,有時彈 Dialog 或者更新某個 View 時拋異常說 Context 已銷燬,因為它需要掛載的上下文已經銷燬了,那麼就沒有上下文來統籌管理這些資源了,自然會拋異常。
- JavaScript
在 JavaScript 中,上下文是指執行上下文,通俗點理解,程式碼執行的上下文,所以也有翻譯成執行環境,可以通俗的把它理解成一個物件,物件名 EC,表示程式碼的執行上下文。
既然理解成一個物件,那麼就有它建立的時機,在 JavaScript 中,每當要執行不同型別的程式碼時,就會建立一個執行上下文 EC。
而程式碼的型別分三種:
- 全域性程式碼
- 函式程式碼
- eval()執行的程式碼
最後一種不討論,全域性程式碼就是指寫在函式外的程式碼,在前端裡,當 HTML 載入一個 js 檔案時,全域性程式碼就會被執行,那麼在全域性程式碼執行前就會先建立一個全域性的執行上下文 EC,之後每呼叫一次函式,要執行函式內的程式碼時,會再建立一個函式執行上下文 EC。
也就是說,不討論 eval 的話,那麼在 JavaScript 有兩種執行上下文,一種是全域性執行上下文,一種是函式執行上下文。
而每次建立一個執行上下文時,都會將其放入一個棧結構,這個棧就稱為執行上下文棧(ECS),也有翻譯成執行環境棧。
所以執行 js 檔案程式碼期間,這個棧底一直是全域性執行上下文,直到 js 檔案程式碼執行結束。全域性程式碼執行過程中,每呼叫一次函式,新建立一個函式執行上下文,就放入棧內。
因此,棧頂就表示當前執行的程式碼,如果棧頂是全域性執行上下文,表示正在執行全域性程式碼;如果棧頂是函式執行上下文,表示正在執行函式內的程式碼。當函式執行結束時,這個函式執行上下文就從棧中移出。
那麼執行上下文(EC)有什麼用呢?
用途可多了,跟 Android 不一樣,Android 裡由於是各種資源的組合使用,但在 JavaScript 中更多的是巢狀函式的變數使用。所以,用途之一就是儲存各個變數。
將 EC 理解成一個物件的話,它有兩個屬性,一個是變數物件(VO),另一個是作用域鏈(Scope Chain)。對於全域性執行上下文,當 HTML 載入一個 js 檔案時,就會建立一個全域性 EC,此時會建立它的兩個屬性:變數物件和作用域鏈。之後,每呼叫一次函式,建立這次函式執行的上下文,函式內部的變數的使用就依賴於這個函式執行上下文中的變數物件和作用域鏈。
也就是說,內部函式之所以可以使用外部函式內的變數,之所以可以使用全域性變數,都是依賴於當前這個內部函式的執行上下文。
而且,變數之所以會提前宣告也是因為執行上下文的因素。這些當講解了執行上下文 EC 的建立過程就清楚了。
變數物件 VO
變數物件只是一個抽象的概念,可以通俗的理解成儲存當前上下文所有變數的物件。
在不同的執行上下文中,它有不同的具體表現。
在全域性執行上下文中,變數物件 VO 的具體表現就是全域性物件,因為所有的全域性變數其實都是全域性物件的屬性,而變數物件 VO 的作用是要儲存當前上下文中的所有變數,所以此時的變數物件 VO 實際上是指向的全域性物件。
尤其在前端中,全域性物件就是 window,所以全域性執行上下文的變數物件 VO = window。
在函式執行上下文中,因為變數物件 VO 是要儲存當前上下文中所有的變數,一個函式內的變數包括:形參變數、區域性變數、自身函式物件引用變數、arguments、this。
針對函式執行上下文,為了儲存這些變數,特意建立了一個物件,稱它為活動物件 AO,函式內所需的變數就都儲存在 AO 中,所以在函式執行上下文中,變數物件 VO 的具體表現也就是 AO。
小結:變數物件 VO 是一個抽象概念,用於儲存當前執行上下文中所有的變數。所以在全域性執行上下文中,因為全域性物件已經儲存著當前上下文所有的變數,所以 VO 在這裡的具體實現就是全域性物件。在函式執行上下文中,由於要儲存函式形參、區域性變數、自身函式物件引用變數、arguments、this,所以新建立了一個叫活動物件 AO 來儲存,此時 VO 的具體實現就是 AO。
作用域鏈
每次函式呼叫時,都會建立一個函式執行上下文 EC,但其中的變數物件 VO 只儲存著當前上下文中的變數而已,那麼函式內如果需要使用到外部函式的變數,甚至是使用全域性的變數時,此時就需要依賴於執行上下文的另一個屬性:作用域鏈。
作用域鏈本質上,其實是將有巢狀層次關係的執行上下文的 VO 拼接起來。
所以大部分場景作用域鏈只有兩個節點:當前函式執行上下文的 VO –> 全域性執行上下文的 VO。所以函式內才可以根據作用域鏈訪問全域性內的變數。
當出現函式內再巢狀函式時,此時作用域鏈就會比較長:
內層函式執行上下文的 VO –> 外層函式執行上下文的 VO –> 全域性執行上下文 VO。
至於作用域鏈是如何將有巢狀層次的執行上下文的 VO 拼接起來,需要藉助函式物件的內部屬性 [[Scope]],[[]]表示執行引擎為物件建立的內部屬性,我們訪問不了,也操作不了。具體原理在作用域鏈一節中講解。
大家好,我是 dasu,歡迎關注我的公眾號(dasuAndroidTv),公眾號中有我的聯絡方式,歡迎有事沒事來嘮嗑一下,如果你覺得本篇內容有幫助到你,可以轉載但記得要關注,要標明原文哦,謝謝支援~