iT邦幫忙

0

[學習筆記] JavaScript 理解Execution Context基本概念以及延伸

  • 分享至 

  • xImage
  •  

本篇內容參考連結

Execution Context

Definition

首先了解其定義, 當JS引擎執行一段程式碼(script)時,便會創造執行環境(execution context). 並執行在下面三種環境之一

-全域 Gloabal : 預設當程式執行時的環境
-函式 Function : 當開始跑函式內部程式碼時的環境
-Eval : 把一串字串當作指令來執行時的環境

用下方程式碼來理解

// Global context, JS 最外層的程式碼部分屬於全域
var greeting = "Hi";

function person() {
  
  // 從大括號開始到結束進入另外一個執行環境
  var _firstName = "Marco";
  var _lastName = "Polo";

  function firstName() {
    // 另外一個執行環境
    return _firstName;
  }
  
  alert(greeting + firstName());
}

function otherPerson() {
  var _firstName = "John";
  var _lastName = "Doe";
}

其中只有一個Global的context, 並代表最上層, 所有的其他的context都可以存取global的狀態以及資料像是程式中的變數greeting. 接下來每一個function都會創造自己的context, 並擁有自己的範圍以及狀態.任何裡面宣告的所有東西皆不能被外面直接存取.像是在otherPerson的執行環境下無法直接存取person得資料_firstName.

Execution Stack

瀏覽器的JS直譯器通常式單執行續(Single Threaded)的意思代表一次只能做一件事, 當在執行其他任務的時候, 其他事件就會被丟在執行序列中等待執行, 而這個我們就稱執行堆疊(execution Stack). 請參考以下程式來理解

 function b() {
 }
 
 function a() {
 }
 
 a();

首先Global Execution Context會被建立, 這時候所有的objects都會一併建立. 在程式開始執行的過程中會因為hoisting(稍後解釋)的緣故, 會先被建立在記憶體上, 接著才會開始逐行執行程式.

接著, 會開始執行a()的部分, 然後會建立a的execution context,並放置到execution stack裡面. 這個execution stack中, 最上面的context會是正在執行的.
alt text

在function a的context建立後, 便會開始執行裡面的內容. 由於裡面會直接到function b, 所以再一次的, 建立的function b的context, 並且stack的最上層, 變成了 function b的 execution context.
alt text

當function b執行完後就會從stack離開,然後再繼續逐行執行funciton a, 然後當執行完畢也依樣會從stack抽離, 最後到global execution context逐行執行.
alt text

Hoisting

一般在寫程式, 都會先定義好變項, 然後才會去使用他, 在尚未定義的情況下直接去使用這個變項, 通常會出現錯誤訊息. 在JS中有個蠻特別的概念就是Hoisting.

w3schools 的定義 : Hoisting is JavaScript's default behavior of moving declarations to the top

在JS中, 會把定義好的變項移到最前面執行, 但實際上並不是真的改變程式的順序, 而是在Compilation phase時, 先把所有定義項(關鍵字如下)儲存在記憶體裡面.

let
const
class
var
function

參考下面的例子

 b(); 
 
 console.log(a);
 
 var a = "Hello";
 
 function b() {
  console.log('Called Function B');
 }

執行的結果會是

Called Function B
undefined

結果並沒有任何錯誤, 首先會先把 var a 和 function b移到最前執行, 這些宣告(declare)的變項都存在記憶體中, 但是a的值("Hello")並沒有存進去, 這使得a的值得到了 undefined. 更精確的說法是, 在定義變項的過程中可分為宣告(declaration)和給值(initialization)的兩個過程. 只有declaration會在逐行執行之前先被執行並儲存在記憶體中(hoisted);給值則是在hoisted後逐行執行程式時才會被執行到.

w3schools 的定義 :
JavaScript Declarations are Hoisted
JavaScript Initializations are Not Hoisted

此外, 假如把宣告a的那行註解掉, 就會得到錯誤 a is not defined.

Hoisting - let, const and class

let, const 和 class實際上也會跟function跟var一樣會做Hoisting. 比較不一樣的是他們的hoisting會在初始化時(execution phase)才會發生. 來比較下面兩個程式

console.log(name) // undefined
var name = "Andrew";
console.log(name); // Uncaught ReferenceError: name is not defined
let name = "Andrew";

var在編譯階段就被提升, 所以在執行階段就會得到已經初始化的undefined, let並沒有在編譯階段做提升, 所以會出現錯誤, 並要等到執行到let name = "Andrew";時, name才會被提升

Variable in Execution Context

首先, 先來看這段程式

function b() {
  var myVar
  console.log(myVar);
}

function a() {
  var myVar = 2;
  b();
  console.log(myVar);
}

var myVar = 1;
console.log(myVar);
a();

得到的結果是 :

1
undefined
2

從建立的邏輯一步一步解釋, 首先Global Execution Context會被建立, 宣告項 function a, function b和var myVar會被執行並存在記憶體, 之後開始逐行執行.

  • var myVar = 1; 會使在Global的myVar值為1,

  • console.log(myVar); 會輸出Global context的值
    alt text

  • a(); 開始建立function a的execution context, 並放進execution stack做堆疊. 然後開始逐行執行

  • var myVar = 2; 因為是在function a裡面的context, 所以不會影響到Global並使值為2
    alt text

  • b(); 執行到function b, 開始建立function b的execution context, 並在execution stack中再次堆疊然後逐行執行

  • var myVar; 在function b的context中僅有宣告, 並未給予任何值

  • console.log(myVar); 因為上一行未給值, 所以這邊會輸出 undefined, 並執行完function b, 然後抽離Execution stack
    alt text

-console.log(myVar); 會輸出在function a的myVar值 2,並執行完和抽離

  • 最後就回到Global execution context.
    alt text

由上面的程式可知, 在不同的execution context宣告同一個變項, 彼此之間是不會影響的. 雖然是同一個名字, 但實際上是三個變項.

補充
在上方程式中, 函式沒有重新宣告變項直接取用,JS Engine在自己的execution context找不到該變項, 則會往他的外層(outer lexical environment)去尋找. 假如上方把function b裡面的宣告註解掉則會得到不一樣的結果.

function b() {
  //var myVar
  console.log(myVar);
}

function a() {
  var myVar = 2;
  b();
  console.log(myVar);
}

var myVar = 1;
console.log(myVar);
a();

得到的結果是 :

1
1
2

Summarize the Execution Context

  • Compilation Phase : 當function被呼叫了, 在開始執行之前
  1. 建立一個scope chain
  2. 建立變數, function, 和參數
  3. 設定this的值

參考andyyou的流程模擬流程

  1. 尋找呼叫 function 的程式碼
  2. 在執行 function 之前建立 執行環境
  3. 進入 建立階段
  • 初始化 scope chain
  • 建立 variable object:
    • 建立 arguments object 檢查執行環境的參數,初始化參數的名稱,值以及建立參考
    • 掃描 function 的宣告
      • 根據找到的每一個 function 在 variable object 建立,在這邊其實就是建立 function 名稱在記憶體中的參考指標
      • 如果 function 名稱已經存在那麼指標就會被覆寫
    • 掃描執行環境裡的變數
      • 每一個變數的宣告都會被加入 variable object 的屬性中,並且初始化為 undefined,注意在這個階段並不會賦值
      • 如果變數名稱存在就略過,繼續處理下一個變數
  • 判斷決定 this 的值
  • Executing Phase : 執行階段, 賦值, 逐行執行

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言