深入理解變數提升和函式提升
說起變數提升和函式提升想必搭建都非常的熟悉。畢竟這是幾個非常基礎,在我們平時的編碼中也經常遇到的問題。那麼我們今天就來深究一下其中的原理。
本文將參考《你不知道的JavaScript 上卷》,在本書作用域一節中對變數提升原理講解非常透徹。
強烈建議大家閱讀。
複製程式碼
編譯原理
儘管通常將 JavaScript 歸類為“動態”或“解釋執行”語言。
但事實上它是一門編譯語言。 這個事實對你來說可能顯而易見,也可能你聞所未聞。
取決於你接觸過多少程式語言,具有多少經驗。
但與傳統的編譯語言不同,它不是提前編譯的。
編譯結果也不能在分散式系統中進行移。
儘管如此,JavaScript引擎進行編譯的步驟和傳統的編譯語言非常相似。
在某些環節可能 比預想的要複雜。
複製程式碼
JS編譯過程分為三步:
- 分詞/詞法分析(Tokenizing/Lexing)
這個過程會將由字元組成的字串分解成(對程式語言來說)有意義的程式碼。
這些代 碼塊被稱為詞法單元(token)。
例如,考慮程式var a = 2;
這段程式通常會被分解成為下面這些詞法單元:var、a、=、2 、;
空格是否會被當作詞法單元,取決於空格在 這門語言中是否具有意義。
複製程式碼
- 解析/語法分析(Parsing)
這個過程是將詞法單元流(陣列)轉換成一個由元素逐級巢狀所組成的代表了程式語法 結構的樹。
這個樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。
var a = 2; 的抽象語法樹中可能會有一個叫作 VariableDeclaration 的頂級節點
接下來是一個叫作Identifier(它的值是 a)的子節點,
以及一個叫作 AssignmentExpression 的子節點。
AssignmentExpression 節點有一個叫作 NumericLiteral(它的值是 2)的子節點。
複製程式碼
- 程式碼生成
將 AST 轉換為可執行程式碼的過程稱被稱為程式碼生成。
這個過程與語言、目標平臺等息 息相關。
拋開具體細節,簡單來說就是有某種方法可以將 var a = 2 的 AST 轉化為一組機器指 令,
用來建立一個叫作 a 的變數(包括分配記憶體等),並將一個值儲存在 a 中。
複製程式碼
我們舉個例子哈
var num = 1
我們在寫程式時,這是一個宣告。但是在引擎和編譯器看來就複雜多了。
編譯器首先會將這段程式分解成詞法單元,然後將詞法單元解析成一個樹結構。
但是當編譯器開始進行程式碼生成時,它對這段程式的處理方式會和預期的有所不同。
可以合理地假設編譯器所產生的程式碼能夠用下面的虛擬碼進行概括:
“為一個變數分配內 存,將其命名為 a,然後將值 2 儲存進這個變數。”
然而,這並不完全正確。
事實上編譯器會進行如下處理。
1. 遇到 var a,編譯器會詢問作用域是否已經有一個該名稱的變數存在於同一個作用域的 集合中。
如果是,編譯器會忽略該宣告,繼續進行編譯;
否則它會要求作用域在當前作 用域的集合中宣告一個新的變數,並命名為 a。
2. 接下來編譯器會為引擎生成執行時所需的程式碼,這些程式碼被用來處理 a = 2 這個賦值操作。
引擎執行時會首先詢問作用域,在當前的作用域集合中是否存在一個叫作 a 的變數。
如果是,引擎就會使用這個變數;
如果否,引擎會繼續查詢該變數。
如果引擎最終找到了 a 變數,就會將 2賦值給它。
否則引擎就會舉手示意並丟擲一個異常!
總結:變數的賦值操作會執行兩個動作。
首先編譯器會在當前作用域中宣告一個變數(如 果之前沒有宣告過)
然後在執行時引擎會在作用域中查詢該變數,如果能夠找到就會對它賦值。
複製程式碼