JavaScript核心概念之執行上下文和棧
桃翁桃翁,問個問題呢,據說 js 裡面有個執行上下文,這個概念是個什麼東東哦?據說挺重要的,給我科普科普唄。
Emm… 這個概念非常的抽象,簡單來說呢,就是 JS 在執行某段程式碼的時候做的一些事情。
具體做的事情就是 定義了變數或函式有權訪問的其他資料決定了它們各自的行為(作用域鏈)。每個執行環境都有一個與之關聯的變數物件(variable object),環境中定義的所有變數和函式都儲存在這個物件中(變數包括 this、arguments)。雖然我們編寫的程式碼無法訪問這個物件,但解析器在處理資料時會在後臺使用它。
哇,還是好抽象啊,你能不能畫個圖舉個栗子呢?
在之前說的執行上下文就是直譯器在執行 JS 某段程式碼的時候做的一些事,那麼首先我們把程式碼分個類。
- Global 程式碼:程式碼第一次執行時預設的環境。
- Function 程式碼:執行到一個函式中。
- Eval 程式碼:文字在eval函式內部執行。
看到這個圖相信現在分清楚各種型別的程式碼,每種型別程式碼會都會產生執行上下文,我們把 Global 程式碼產生的執行環境叫 「全域性執行上下文」 ,把 Function 程式碼產生的執行環境叫 「執行上下文」 吧,Eval 程式碼不考慮。
那我看這個圖似乎有很多執行上下文(execution context),這個具體是怎麼來的呢?
全域性執行上下文只有 一個 ,而執行環境的話是 每次函式呼叫都會產生一個執行上下文 。注意要呼叫才會產生哦, 不呼叫是不會產生的。
那這個執行上下文基本知道是個什麼東西了,那執行上下文棧又是啥呢?
見名知意,執行上下文棧就是執行上下文(包含全域性執行上下文)形成的棧嘛。
那為什麼要有這個執行上下文棧呢?
瀏覽器中 JavaScript 直譯器是單執行緒的,這就是說同一時間程式碼只會做一件事,那麼建立這麼多執行上下文,又不能同一時間執行多個上下文,所以就必須要有個順序,這個順序就是就是先進後出,這很明顯就是一個棧結構嘛。
那我就疑惑了,為啥要先進後出,不先進先出呢?
我們分析一下圖一的程式碼,結合上圖,首先我們看圖 1,解釋程式碼的時候首先建立的就是全域性上下文,然後再建立 person 的執行上下文,然後再建立 firstName 的上下文,然後再執行完畢 firstName ,就把 firstName 的上下文彈出,再 建立 lastName 的上下文,然後執行完畢,再彈出 lastName 的上下文,然後執行完 person 的上下文,再彈出 person 的上下文,再執行全域性上下文,然後全域性上下文彈出。
如下是一張經典的執行上下文棧的圖。
預設進入全域性上下文。如果你的全域性程式碼中呼叫了一個函式,那麼程式將會進入這個被呼叫函式的上下文,建立一個新的執行上下文,並把當前上下文放到棧頂。瀏覽器總是會把當前執行上下文放到棧的頂部,一旦函式執行完成,這個執行上下文就會從棧中移除,返回到棧中的下一個上下文。
這些大概明白了,不過你說在建立執行上下文做的那些事兒,我還是有點迷糊,能再詳細說說嗎?
那我們首先看點程式碼:
// 例1 console.log(a); // 報錯,a is not defined
// 例2 console.log(a); // undefined var a;
// 例 3 console.log(a); // undefined var a = 666;
// 例 4 console.log(this); // window 物件
// 例 5 function foo(x) { console.log(arguments); // [666] console.log(x); // 666 } foo(666);
// 例 6 // 函式表示式 console.log(foo); // undefined var foo = function foo() {}
// 例 7 // 函式宣告 console.log(foo); // function() {} function foo() {}
這 7 個例子相信大家對這些答案都是沒有疑惑的,最基礎的東西,例 1 報錯,a 未定義,很正常。例 2、例 3 輸出都是 undefined,說明瀏覽器在執行 console.log(a) 時,已經知道了 a 是 undefined,但卻不知道 a 是 666(例 3)。
看例 4 就知道,當執行這條語句的時候 this 已經被賦值了。
在例 5 中展示了在函式體的語句執行之前,arguments 變數和函式的引數都已經被賦值。從這裡可以看出,函式每被呼叫一次,都會產生一個新的執行上下文環境。因為不同的呼叫可能就會有不同的引數。
然後就是例 6,例 7 中可以看出函式表示式跟變數宣告一樣,只是給變數賦值成 undefined ,而函式宣告會將會把函式整個賦值了。
總結在執行上下文做的賦值事情
- 變數、函式表示式——變數宣告,預設賦值為undefined;
- this——賦值;
- 函式宣告——賦值;
執行上下文就介紹到這裡,如果你對相關知識還是感到迷惑,比如當在建立執行上下文的時候還有作用域,以及變數物件等概念,後面再一一介紹,不要擔心,跟著我的文章走,這塊一定能啃動。