一、編譯原理javascript
分詞/詞法分析( Tokenizing/Lexing)java
這個過程會將由字符組成的字符串分解成( 對編程語言來講) 有意義的代碼塊, 這些代碼塊被稱爲詞法單元( token)。 例如, 考慮程序 var a = 2;。 這段程序一般會被分解成爲下面這些詞法單元: var、 a、 =、 2 、 ;。 空格是否會被看成詞法單元, 取決於空格在這門語言中是否具備意義。git
二、理解做用域github
當你看到var a=2;
這個代碼段的時候,你也許只會認爲這是一個聲明語句,可是事實上,瀏覽器引擎並不會這麼認爲!其實瀏覽器會這麼認爲:編程
一、遇到 var a, 編譯器會詢問做用域是否已經有一個該名稱的變量存在於同一個做用域的集合中。 若是是, 編譯器會忽略該聲明, 繼續進行編譯; 不然它會要求做用域在當前做用域的集合中聲明一個新的變量, 並命名爲 a。瀏覽器
二、接下來編譯器會爲引擎生成運行時所需的代碼, 這些代碼被用來處理 a = 2 這個賦值操做。 引擎運行時會首先詢問做用域, 在當前的做用域集合中是否存在一個叫做 a 的變量。 若是是, 引擎就會使用這個變量; 若是否, 引擎會繼續查找該變量。編程語言
三、做用域嵌套函數
當一個塊或函數嵌套在另外一個塊或函數中時, 就發生了做用域的嵌套。 所以, 在當前做用域中沒法找到某個變量時, 引擎就會在外層嵌套的做用域中繼續查找, 直到找到該變量,或抵達最外層的做用域( 也就是全局做用域) 爲止。ui
function foo(a) {
console.log(a + b);
}
var b = 3;
foo(3);複製代碼
遍歷嵌套做用域鏈的規則很簡單: 引擎從當前的執行做用域開始查找變量, 若是找不到,就向上一級繼續查找。 當抵達最外層的全局做用域時, 不管找到仍是沒找到, 查找過程都會中止。spa
四、當即執行函數
var a = 3;
(function IIFE() {
var a = 4;
console.log(a);
})();
console.log(a);複製代碼
因爲函數被包含在一對 ( ) 括號內部, 所以成爲了一個表達式, 經過在末尾加上另一個( ) 能夠當即執行這個函數, 好比 (function foo(){ .. })()。 第一個 ( ) 將函數變成表達式, 第二個 ( ) 執行了這個函數。
相較於傳統的 IIFE 形式, 不少人都更喜歡另外一個改進的形式: (function(){ .. }())。 仔
細觀察其中的區別。 第一種形式中函數表達式被包含在 ( ) 中, 而後在後面用另外一個 () 括號來調用。 第二種形式中用來調用的 () 括號被移進了用來包裝的 ( ) 括號中。這兩種形式在功能上是一致的。 選擇哪一個全憑我的喜愛。
提高
考慮如下代碼:
a=2;
var a;
console.log(a);複製代碼
你認爲 console.log(..) 聲明會輸出什麼呢?
不少開發者會認爲是 undefined, 由於 var a 聲明在 a = 2 以後, 他們天然而然地認爲變量被從新賦值了, 所以會被賦予默認值 undefined。 可是, 真正的輸出結果是 2。
考慮另一段代碼:
console.log(a);
var a=2;複製代碼
鑑於上一個代碼片斷所表現出來的某種非自上而下的行爲特色, 你可能會認爲這個代碼片斷也會有一樣的行爲而輸出 2。 還有人可能會認爲, 因爲變量 a 在使用前沒有先進行聲明,所以會拋出 ReferenceError 異常。
不幸的是兩種猜想都是不對的。 輸出來的會是 undefined。
那麼到底發生了什麼? 看起來咱們面對的是一個先有雞仍是先有蛋的問題。 究竟是聲明( 蛋) 在前, 仍是賦值( 雞) 在前?
正確的思考思路是, 包括變量和函數在內的全部聲明都會在任何代碼被執行前首先被處理。當你看到 var a = 2; 時, 可能會認爲這是一個聲明。 但 JavaScript 實際上會將其當作兩個聲明: var a; 和 a = 2;。 第一個定義聲明是在編譯階段進行的。 第二個賦值聲明會被留在原地等待執行階段。
咱們的第一個代碼片斷會以以下形式進行處理:
var a;
a = 2;
console.log( a );複製代碼
其中第一部分是編譯, 而第二部分是執行。
相似地, 咱們的第二個代碼片斷實際是按照如下流程處理的:
var a;
console.log( a );
a = 2;複製代碼
所以, 打個比方, 這個過程就好像變量和函數聲明從它們在代碼中出現的位置被「 移動」到了最上面。 這個過程就叫做提高。換句話說, 先有蛋( 聲明) 後有雞( 賦值)。
只有聲明自己會被提高, 而賦值或其餘運行邏輯會留在原地。 若是提高改變了代碼執行的順序, 會形成很是嚴重的破壞。
五、函數優先
函數聲明和變量聲明都會被提高。 可是一個值得注意的細節( 這個細節能夠出如今有多個「 重複」 聲明的代碼中) 是函數會首先被提高, 而後纔是變量。
考慮如下代碼:
foo();
var foo;
function foo() {
console.log(1);
}
foo = function () {
console.log(2);
};複製代碼
會輸出 1 而不是 2 ! 這個代碼片斷會被引擎理解爲以下形式:
function foo() {
console.log(1);
}
foo();
foo = function () {
console.log(2);
};複製代碼
注意, var foo 儘管出如今 function foo()... 的聲明以前, 但它是重複的聲明( 所以被忽略了), 由於函數聲明會被提高到普通變量以前。
儘管重複的 var 聲明會被忽略掉, 但出如今後面的函數聲明仍是能夠覆蓋前面的。
foo();//3
function foo() {
console.log(1);
}
var foo = function () {
console.log(2);
};
function foo() {
console.log(3);
}複製代碼
歡迎關注個人GiHutb:github.com/HuangQinJia…
我的博客:blog.csdn.net/sinat_35512…
歡迎掃描下方二維碼關注個人GtHub以及我的博客