js——作用域和閉包
1. js是編譯語言,但是它不是提前編譯,編譯結果不能在分布式系統中移植。大部分情況下,js的編譯發生在代碼執行前的幾微秒(甚至更短)
2. 一般的編譯步驟
- 分詞/詞法分析:把字符串分解成詞法單元
- 解析/語法分析:將詞法單元轉換成一個由元素組成的語法結構樹,抽象語法樹AST
- 代碼生成:將AST轉換成一組機器指令
3. 三個工具
- 引擎:控制整個程序的編譯及執行過程
- 編譯器:負責語法分析及代碼生成等
- 作用域:收集並維護所有聲明的標識符的訪問權限
4. var a = 2 的編譯過程
var a=2; | --分解成--> |
詞法單元 var a = 2; => var、a、=、2 |
--解析成--> |
樹結構 AST |
--代碼生成--> 1. var a:詢問作用域是否有a。 如果有,則忽略。如果沒有,則在當前作用域添加一個聲明a 2. a = 2:當前作用域是否有a, 如果有,則賦值。如果沒有,則向上一層作用域查找 |
5. 代碼生成中查找判斷作用域是否存在某個變量的兩種查找類型
LHS | RHS | |
直觀區別 | 變量在=左側 | 變量不在=左側 |
操作 | 對變量賦值 | 取變量的值 |
找不到? |
1. 嚴格模式 拋出ReferenceError異常 2. 非嚴格模式 自動隱式創建一個全局變量 |
拋出ReferenceError異常 |
6. 作用域
- 詞法作用域
定義在詞法階段的作用域。也就是在寫代碼時將變量和塊作用域寫在哪決定的。函數的作用域完全由聲明時的位置決定
-
- 全局作用域
- 函數作用域:每聲明一個函數就會創建一個作用域。在該作用域內聲明的變量或函數(標識符)都附屬於它,可在整個函數範圍內被使用。
- 塊作用域
var |
//變量綁定在所在的函數內 (function |
try/catch |
try{ //異常操作 //catch創建一個塊作用域,這裏的變量只能在catch中使用 }catch(err){ //只有這裏可以訪問err } |
let |
ES6新引入的。將變量綁定在所在任意作用域{}中 在循環中for(let i = 1; i < 5; i++),i在每次叠代中會聲明,且每次叠代會用上一個叠代結束時的值來初始化 |
const | ES6新引入的,將變量綁定在所在任意作用域{}中,且值是固定不可修改的 |
- 運行時修改作用域 eval、with
7. 作用域嵌套
在一個作用域A內創建一個新的作用域B,則B被嵌套在A中
B可以訪問A中的標識符。A不可以訪問B中的標識符
最外層的作用域是全局作用域
作用域層層嵌套形成作用域鏈,在訪問查找一個標識符時從最內層開始向外查找,一旦找到就停止,因此會出現外層的標識符被內層同名的所屏蔽
8. 閉包
在各個文章中對閉包進行了解釋,但是好像有很多說法。我理解得了的一個說法是:
當函數可以記住並訪問所在的詞法作用域時,就產生了閉包
函數A創建一個作用域A,在A中聲明一個函數B(創建了作用域B),把函數B作為結果返回,作用域B會記得自己的作用域鏈,利用B可以向上層作用域訪問
9. 循環和閉包(一個好像特別常見的例子)
for( var i = 1; i <= 5; i++){ setTimeout(function timer(){ console.log(i); }, i*1000);}
說明:
var i = 1:定義了一個全局變量
setTimeout():在i秒後執行timer函數。timer是回調函數,會在for循環執行完成才會開始調用
結果:for執行完成後開始調用timer,以每秒一次的頻率輸出5次6
期待:每秒一次輸出1,2,3,4,5
結果解釋:
setTimeout時並沒有讓timer保存i的副本
timer函數執行時,會去引用i的值,這時只有一個i=6
修改1——立即執行
for(var i = 1; i <=5; i++){ (function(){ setTimeout(function timer(){ console.log(i); }, i*1000); })();}
結果:以每秒一次的頻率輸出5次6
即使是立即執行,最後訪問的變量也是全局的i
修改2——立即執行+參數
for(var i = 1; i <=5; i++){ (function(j){ setTimeout(function timer(){ console.log(i); }, i*1000); })(i);}
結果:每秒一次輸出1,2,3,4,5
修改3——塊作用域循環變量
for(let i = 1; i <=5; i++){ setTimeout(function timer(){ console.log(i); }, i*1000); }
結果:每秒一次輸出1,2,3,4,5
修改4——在循環中添加一個變量var j
for(var i = 1; i <=5; i++){ var j = i; setTimeout(function timer(){ console.log(j); }, j*1000); }
結果:以每秒一次的頻率輸出5次5(j和i一樣為全局變量,j=5)
修改5——塊作用域變量
for(var i = 1; i <=5; i++){ let j = i; setTimeout(function timer(){ console.log(j); }, j*1000); }
結果:每秒一次輸出1,2,3,4,5
參考
1. 《你不知道的javascript》上卷
2. 還看了很多網上的說明,就不一一列舉了,因為沒記住具體哪些了
js——作用域和閉包