你不知道的js上卷知識歸納1
作用域和閉包
1.作用域是什麼
前言
夠儲存變數當中的值,並且能在之後對這個值進行訪問或修改。這樣的能力將狀態帶給了程式。沒有這樣的能力程式設計收到了高度的限制。
上述的言論由此引入了話題:什麼是變數,變數儲存在哪裡,如何在程式需要時如何找到它們?
劃重點:這章簡單說道了編譯原理這個話題,引出了傳統的編譯語言的流程,道出了javascript與傳統編譯語言的編譯的流程不同(js沒有那麼多的時間來優化),也簡單的說了js中大致編譯的流程,通過js中的演員表對話中簡單講述了js編譯的流程,通過對話具象化了js作用域是幹嘛的。
1.1編譯原理
傳統程式語言流程:
- 詞法分析:這個過程會將由字元組成的字串分解成詞法單元
- 語法分析:這個過程是將詞法單元流(陣列)轉換成一個由元素逐級巢狀所組成的代表了程式語法結構的樹(抽象語法樹AST)
- 程式碼生成:AST轉換為可執行程式碼的過程,拋開具體細節,簡單來說就是有某種方法可以將
var a = 2;
的AST 轉化為一組機器指令,用來建立一個叫作 a 的變數(包括分配記憶體等),並將一個值儲存在 a 中。
JavaScript引擎沒有大量的(像其他語言編譯器那麼多的)時間用來進行優化,JavaScript的編譯過程不是發生在構建之前的,JavaScript來說,大部分情況下編譯發生在程式碼執行前的幾微秒(甚至更短!)的時間內。換句簡單的說就是任JavaScript 程式碼片段在執行前都要進行編譯(通常就在執行前)。因此, JavaScript 編譯器首先會對 var a = 2; 這段程式進行編譯,然後做好執行它的準備,並且 通常馬上就會執行它。
1.2理解作用域
當js遇見var a = 2;
發生了什麼?
- 引擎:從頭到尾負責整個JavaScript程式的編譯及執行過程
- 編譯器:負責語法分析及程式碼生成等髒活累活
- 作用域:負責收集並維護由所有宣告的識別符號(變數)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的程式碼對這些識別符號的訪問許可權。
1.遇到var a
,編譯器會詢問作用域是否已經有一個該名稱的變數存在於同一個作用域的集合中。如果是,編譯器會忽略該宣告,=,繼續進行編譯;否則它會要求作用域在當前作 用域的集合中宣告一個新的變數,並命名為 a
。
- 接下來編譯器會為引擎生成執行時所需的程式碼,這些程式碼被用來處理
a = 2
總結:變數的賦值操作會執行兩個動作,首先編譯器會在當前作用域中宣告一個變數(如 果之前沒有宣告過),然後在執行時引擎會在作用域中查詢該變數,如果能夠找到就會對 它賦值。
1.3作用域巢狀
作用域是根據名稱查詢變數的一套規則,當一個塊或函式巢狀在另一個塊或函式中時,就發生了作用域的巢狀,在當前作用域中無法找到某個變數時,引擎就會在外層巢狀的作用域中繼續查詢,直到找到該變數,或抵達最外層的作用域(也就是全域性作用域)為止。
LHS:賦值操作的目標是誰
RHS:誰是賦值操作的源頭
RHS查詢在所有巢狀的作用域中遍尋不到所需的變數引擎就會丟擲ReferenceError
異常。
當引擎執行LHS查詢時,如果在頂層(全域性作用域)中也無法找到目標變數,全域性作用域中就會建立一個具有該名稱的變數,並將其返還給引擎,前提是程式執行在非 “嚴格模式”下。
簡單區分LHS和RHS:如果查詢的目的是對變數進行賦值,那麼就會使用LHS查詢,=操作符或呼叫函式時傳入引數的操作都會導致關聯作用域的賦值操作。如果目的是獲取變數的值,就會使用RHS查詢console.log( a );
1.4異常
- ReferenceError
- TypeError
1.5小結
作用域是一套規則,用於確定在何處以及如何查詢變數(識別符號)。如果查詢的目的是對
變數進行賦值,那麼就會使用 LHS 查詢;如果目的是獲取變數的值,就會使用 RHS 查詢。
賦值操作符會導致LHS查詢。=操作符或呼叫函式時傳入引數的操作都會導致關聯作用域 的賦值操作。
JavaScript引擎首先會在程式碼執行前對其進行編譯,在這個過程中,像var a = 2
這樣的聲 明會被分解成兩個獨立的步驟:
- 首先,var a 在其作用域中宣告新變數。這會在最開始的階段,也就是程式碼執行前進行。
- 接下來,a = 2 會查詢(LHS 查詢)變數 a 並對其進行賦值。LHS 和 RHS 查詢都會在當前執行作用域中開始,如果有需要(也就是說它們沒有找到所 需的識別符號),就會向上級作用域繼續查詢目標識別符號,這樣每次上升一級作用域(一層 樓),最後抵達全域性作用域(頂層),無論找到或沒找到都將停止。
不成功的 RHS 引用會導致丟擲 ReferenceError 異常。不成功的LHS引用會導致自動隱式 地建立一個全域性變數(非嚴格模式下),該變數使用 LHS 引用的目標作為識別符號,或者拋 出 ReferenceError 異常(嚴格模式下)。