《你不知道的JavaScript》讀書筆記(一)

HuangQinJian發表於2017-03-20

1、編譯原理

分詞/詞法分析( Tokenizing/Lexing)

這個過程會將由字元組成的字串分解成( 對程式語言來說) 有意義的程式碼塊, 這些程式碼塊被稱為詞法單元( token)。 例如, 考慮程式 var a = 2;。 這段程式通常會被分解成為下面這些詞法單元: var、 a、 =、 2 、 ;。 空格是否會被當作詞法單元, 取決於空格在這門語言中是否具有意義。


2、理解作用域

當你看到var a=2;這個程式碼段的時候,你也許只會認為這是一個宣告語句,但是事實上,瀏覽器引擎並不會這麼認為!其實瀏覽器會這麼認為:

1、遇到 var a, 編譯器會詢問作用域是否已經有一個該名稱的變數存在於同一個作用域的集合中。 如果是, 編譯器會忽略該宣告, 繼續進行編譯; 否則它會要求作用域在當前作用域的集合中宣告一個新的變數, 並命名為 a。

2、接下來編譯器會為引擎生成執行時所需的程式碼, 這些程式碼被用來處理 a = 2 這個賦值操作。 引擎執行時會首先詢問作用域, 在當前的作用域集合中是否存在一個叫作 a 的變數。 如果是, 引擎就會使用這個變數; 如果否, 引擎會繼續查詢該變數。


3、作用域巢狀

當一個塊或函式巢狀在另一個塊或函式中時, 就發生了作用域的巢狀。 因此, 在當前作用域中無法找到某個變數時, 引擎就會在外層巢狀的作用域中繼續查詢, 直到找到該變數,或抵達最外層的作用域( 也就是全域性作用域) 為止。

function foo(a) {
    console.log(a + b);
}
var b = 3;
foo(3);複製程式碼

《你不知道的JavaScript》讀書筆記(一)
這裡寫圖片描述

遍歷巢狀作用域鏈的規則很簡單: 引擎從當前的執行作用域開始查詢變數, 如果找不到,就向上一級繼續查詢。 當抵達最外層的全域性作用域時, 無論找到還是沒找到, 查詢過程都會停止。


4、立即執行函式

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。

《你不知道的JavaScript》讀書筆記(一)
這裡寫圖片描述

考慮另外一段程式碼:

console.log(a);
var a=2;複製程式碼

鑑於上一個程式碼片段所表現出來的某種非自上而下的行為特點, 你可能會認為這個程式碼片段也會有同樣的行為而輸出 2。 還有人可能會認為, 由於變數 a 在使用前沒有先進行宣告,因此會丟擲 ReferenceError 異常。

不幸的是兩種猜測都是不對的。 輸出來的會是 undefined。

《你不知道的JavaScript》讀書筆記(一)
這裡寫圖片描述

那麼到底發生了什麼? 看起來我們面對的是一個先有雞還是先有蛋的問題。 到底是宣告( 蛋) 在前, 還是賦值( 雞) 在前?

正確的思考思路是, 包括變數和函式在內的所有宣告都會在任何程式碼被執行前首先被處理。當你看到 var a = 2; 時, 可能會認為這是一個宣告。 但 JavaScript 實際上會將其看成兩個宣告: var a; 和 a = 2;。 第一個定義宣告是在編譯階段進行的。 第二個賦值宣告會被留在原地等待執行階段。

我們的第一個程式碼片段會以如下形式進行處理:

var a;
a = 2;
console.log( a );複製程式碼

其中第一部分是編譯, 而第二部分是執行。

類似地, 我們的第二個程式碼片段實際是按照以下流程處理的:

var a;
console.log( a );
a = 2;複製程式碼

因此, 打個比方, 這個過程就好像變數和函式宣告從它們在程式碼中出現的位置被“ 移動”到了最上面。 這個過程就叫作提升。換句話說, 先有蛋( 宣告) 後有雞( 賦值)。

只有宣告本身會被提升, 而賦值或其他執行邏輯會留在原地。 如果提升改變了程式碼執行的順序, 會造成非常嚴重的破壞。


5、函式優先

函式宣告和變數宣告都會被提升。 但是一個值得注意的細節( 這個細節可以出現在有多個“ 重複” 宣告的程式碼中) 是函式會首先被提升, 然後才是變數。

考慮以下程式碼:

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以及個人部落格

《你不知道的JavaScript》讀書筆記(一)
GitHub

《你不知道的JavaScript》讀書筆記(一)
CSDN部落格

《你不知道的JavaScript》讀書筆記(一)
個人主頁

相關文章