1. 程式人生 > >js——作用域和閉包

js——作用域和閉包

如果 分布式系統 ren 移植 font 寫代碼 一次 運行時 屏蔽

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
c(){ //a是局部變量,b是全局變量   var a = b = 3; })() (function c(){ //a和b都是局部變量   var a = 1, b = 3; })()
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——作用域和閉包