天天看點

JavaScript核心概念之執行上下文和棧

現在想改變一下寫作方式,以問答的形式來講解這些枯燥無味的知識,盡量把每一個為什麼都講透,每個知識點都不迷惑。

桃翁桃翁,問個問題呢,據說 js 裡面有個執行上下文,這個概念是個什麼東東哦?據說挺重要的,給我科普科普呗。

Emm… 這個概念非常的抽象,簡單來說呢,就是 JS 在執行某段代碼的時候做的一些事情。

具體做的事情就是定義了變量或函數有權通路的其他資料決定了它們各自的行為(作用域鍊)。每個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的所有變量和函數都儲存在這個對象中(變量包括 this、arguments)。雖然我們編寫的代碼無法通路這個對象,但解析器在處理資料時會在背景使用它。

哇,還是好抽象啊,你能不能畫個圖舉個栗子呢?

在之前說的執行上下文就是解釋器在執行 JS 某段代碼的時候做的一些事,那麼首先我們把代碼分個類。

Global 代碼:代碼第一次執行時預設的環境。

Function 代碼:執行到一個函數中。

Eval 代碼:文本在eval函數内部執行。

JavaScript核心概念之執行上下文和棧

看到這個圖相信現在厘清楚各種類型的代碼,每種類型代碼會都會産生執行上下文,我們把 Global 代碼産生的執行環境叫「全局執行上下文」,把 Function 代碼産生的執行環境叫「執行上下文」吧,Eval 代碼不考慮。

那我看這個圖似乎有很多執行上下文(execution context),這個具體是怎麼來的呢?

全局執行上下文隻有一個,而執行環境的話是每次函數調用都會産生一個執行上下文。注意要調用才會産生哦,不調用是不會産生的。

那這個執行上下文基本知道是個什麼東西了,那執行上下文棧又是啥呢?

見名知意,執行上下文棧就是執行上下文(包含全局執行上下文)形成的棧嘛。

那為什麼要有這個執行上下文棧呢?

浏覽器中 JavaScript 解釋器是單線程的,這就是說同一時間代碼隻會做一件事,那麼建立這麼多執行上下文,又不能同一時間執行多個上下文,是以就必須要有個順序,這個順序就是就是先進後出,這很明顯就是一個棧結構嘛。

那我就疑惑了,為啥要先進後出,不先進先出呢?

JavaScript核心概念之執行上下文和棧

我們分析一下圖一的代碼,結合上圖,首先我們看圖 1,解釋代碼的時候首先建立的就是全局上下文,然後再建立 person 的執行上下文,然後再建立 firstName 的上下文,然後再執行完畢 firstName ,就把 firstName 的上下文彈出,再 建立 lastName 的上下文,然後執行完畢,再彈出 lastName 的上下文,然後執行完 person 的上下文,再彈出 person 的上下文,再執行全局上下文,然後全局上下文彈出。

如下是一張經典的執行上下文棧的圖。

JavaScript核心概念之執行上下文和棧

預設進入全局上下文。如果你的全局代碼中調用了一個函數,那麼程式将會進入這個被調用函數的上下文,建立一個新的執行上下文,并把目前上下文放到棧頂。浏覽器總是會把目前執行上下文放到棧的頂部,一旦函數執行完成,這個執行上下文就會從棧中移除,傳回到棧中的下一個上下文。

這些大概明白了,不過你說在建立執行上下文做的那些事兒,我還是有點迷糊,能再詳細說說嗎?

那我們首先看點代碼:

// 例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——指派;

函數聲明——指派;

執行上下文就介紹到這裡,如果你對相關知識還是感到迷惑,比如當在建立執行上下文的時候還有作用域,以及變量對象等概念,後面再一一介紹,不要擔心,跟着我的文章走,這塊一定能啃動。

原文釋出時間為:2018-09-21

原文作者: 桃翁

本文來自雲栖社群合作夥伴“ 

前端桃園

”,了解相關資訊可以關注“

http://mp.weixin.qq.com/profile?src=3×tamp=1537844814&ver=1&signature=gYQkIvW5JxXFYf-hoTqNci*3ol2GLZmcai2gWTIwme-JEIVRKYp8wxYW6MdKpuRXwKYxmgtO79FcUETsGw*eIg==

”。

繼續閱讀