JS執行上下文詳解(一):作用域

作者 | Elya
轉載請註明出處:https://www.toutiao.com/i6792922868691763724/

本文主要介紹JS執行上下文相關的內容,理解了JavaScript的執行上下文才能更好地理解JavaScript語言本身以及該語言一些特性,如變量提升、作用域和閉包。

一、作用域

1.1 作用域

作用域是指程序中變量定義的區域,該位置決定了變量的生命週期。通俗地說,作用域就是變量與函數的可訪問範圍,作用域控制著變量和函數的可見性和生命週期。

JavaScript在設計之初並未想過這門語言會如此受歡迎,所以只是按照最簡單的方式來設計,只保留全局作用域和函數作用域。

  • 全局作用域中對象和變量在任何地方都能訪問,其生命週期同頁面的生命週期。
  • 函數作用域是在函數內部定義的變量或函數,只能在函數內部被訪問。函數執行結束之後,函數內部定義的變量會被銷燬。

1.2 變量提升

JavaScript在執行前會先進行編譯,編譯階段會將變量和函數存放在執行上下文的變量環境中,值設為​undefined​。編譯完成進入執行階段,使用變量時從變量環境中讀取,在執行變量的賦值代碼前,讀取該變量時值均為默認值 ​undefined​。

變量提升給初學者帶來了很多疑惑,有如下問題:

  • 變量容易在不被察覺的情況下被覆蓋掉
<code>var myname = "JavaScript";function showName(){  console.log(myname); // undefined  if(0){  var myname = "Go"  }  console.log(myname);}showName();/<code>
  • 本應銷燬的變量沒有被銷燬
<code>function foo(){     for (var i = 0; i < 7; i++) {     }    console.log(i); // for循環結束後i變量沒有被銷魂}foo();/<code>

1.3 塊級作用域

為了解決變量提升等問題、打造JavaScript為企業級的開發語言,ES6引入了 ​let​ 和 ​const​ 關鍵字,從而使 JavaScript 也能像其他語言一樣擁有了塊級作用域。

我們已經知道 JavaScript 引擎是通過變量環境實現函數級作用域的,那麼 ES6 又是如何在函數級作用域的基礎之上,實現對塊級作用域的支持呢?

我們來看個例子:

<code>function foo() {    var a = 1;    let b = 2;    {        let b = 3;        var c = 4;        let d = 5;        console.log(a);        console.log(b);    }    console.log(b);    console.log(c);    console.log(d)}foo();/<code>

當執行上面這段代碼的時候,JavaScript 引擎會先對其進行編譯並創建執行上下文,然後再按照順序執行代碼,現在我們引入了 ​let​ 關鍵字,​let​ 關鍵字會創建塊級作用域,那麼 let 關鍵字是如何影響執行上下文的呢?

接下來我們就來一步步分析上面這段代碼的執行流程。


第一步是編譯並創建執行上下文,執行上下文如下圖所示:

瀏覽器原理系列 - JS執行上下文詳解(一):作用域

從上圖可以看出:

  • 函數內部通過 ​var聲明的變量,在編譯階段會被存放到變量環境中。
  • 通過 ​let聲明的變量,在編譯階段會存放到詞法環境中。
  • 在函數的作用域內部,通過 ​let​ 聲明的變量並沒有被存放到詞法環境中。

第二步是執行代碼,當執行到函數代碼塊裡面時(第4行),當執行到代碼塊裡面時,變量環境中 a 的值已經被設置成了 1,詞法環境中 b 的值已經被設置成了 2,這時候函數的執行上下文就如下圖所示:

瀏覽器原理系列 - JS執行上下文詳解(一):作用域

從圖中可以看出,當進入函數的作用域塊時,作用域塊中通過 ​let​ 聲明的變量,會被存放在詞法環境的一個單獨的區域中,這個區域中的變量並不影響作用域塊外面的變量,比如在作用域外面聲明瞭變量 b,在該作用域塊內部也聲明瞭變量 b,當執行到作用域內部時,它們都是獨立的存在。

其實,在詞法環境內部,維護了一個小型棧結構,棧底是函數最外層的變量,進入一個作用域塊後,就會把該作用域塊內部的變量壓到棧頂;當作用域執行完成之後,該作用域的信息就會從棧頂彈出,這就是詞法環境的結構。需要注意下,我這裡所講的變量是指通過 ​let​ 或者 ​const​ 聲明的變量。

再接下來,當執行到作用域塊中的 ​console.log(a)​ 這行代碼時,就需要在詞法環境和變量環境中查找變量 a 的值了,具體查找方式是:沿著詞法環境的棧頂向下查詢,如果在詞法環境中的某個塊中查找到了,就直接返回給 JavaScript 引擎,如果沒有查找到,那麼繼續在變量環境中查找。

當作用域塊執行結束之後,其內部定義的變量就會從詞法環境的棧頂彈出,最終執行上下文如下圖所示:

瀏覽器原理系列 - JS執行上下文詳解(一):作用域

總結一下,塊級作用域就是通過詞法環境的棧結構來實現的,而變量提升是通過變量環境來實現,通過這兩者的結合,JavaScript 引擎也就同時支持了變量提升和塊級作用域了。


分享到:


相關文章: