1. 程式人生 > >犀牛書學習筆記2:作用域

犀牛書學習筆記2:作用域

  作用域(scope):
  • 詞法作用域(lexical scope)(3.10.1節) 
——函式作用域、全域性變數和區域性變數 JavaScript是基於詞法作用域(lexical scope,也成為靜態作用域 static scope),在JavaScript中,一個變數的作用域就是程式原始碼中定義這個變數的區域。 變數在這個“區域”中可訪問,超出了這個“區域”,則無法被訪問。 與詞法作用域相對的是動態作用域(dynamic scope),變數的作用域由程式執行時的狀態決定。   JavaScript有兩種作用域:
    • 在函式外宣告的變數叫全域性變數,全域性變數在全域性都有定義,擁有全域性作用域(globe scope)。
    • 在函式內宣告的變數叫區域性變數,區域性變數在區域性(函式體內)有定義,超出了這個“區域”,則無法被訪問,區域性變數擁有區域性作用域(local scope)。
  例:宣告一個全域性變數和一個區域性變數,全域性變數在全域性都有定義,全域性都可以訪問,而區域性變數超出了“有效範圍”(checkscope函式體內),在作用域外則無法被訪問。變數和函式的作用域都是如此。 var  globalScope = "global";            // 宣告一個全域性變數
function checkscope() {                 // 宣告一個全域性函式     var localScope = "local";           // 宣告一個區域性變數 } globalScope;                            // =>"global"  獲得全域性變數
//localScope;                           // => Uncaught ReferenceError: checlscope is not defined  
  • 函式作用域和塊級作用域
用var關鍵字宣告的變數和函式宣告語句定義的函式,只有全域性作用域和區域性(函式)作用域(function scope),沒有塊作用域(block scope)。 {var v = 1} console.log(v);  // 沒有塊級作用域,變數沒有被定義在任何函式體內,是全域性變數,所以函式塊外面程式碼可以訪問變數 for(var v6 = 1;v6<10;v6++){} console.log(v6)  // v6沒有被定義在任何函式體內,全域性變數,for迴圈體外也可以被訪問  
  • 區域性變數的優先順序高於同名的全域性變數。(@@@有作用域鏈維護)
var scope = "global";          // 宣告一個全域性變數 function checkscope(){    var scope = "local";        // 宣告一個同名的區域性變數,在該函式體內,這個區域性變數覆蓋了同名的全域性變數(@@@由作用域鏈維護)    return scope;               // 返回這個區域性變數 } scope;                         // => "global" checkscope();                  // => "local"  
  • 每個函式都有它自己的作用域
var scope = 'global';           // 宣告一個全域性變數 function checkscope_1(){     var scope = 'local_1';     return scope; } function checkscope_2(){     var scope = 'local_2';     return scope; } scope;                          // => "global" checkscope_1();                 // => "local_1" checkscope_2();                 // => "local_2"  
  • 巢狀作用域
函式定義是可以巢狀的,由於每個函式都有它自己的作用域,因此會出現幾個作用域巢狀的情況。 var scope = "global scope";           // 全域性變數 function checkscope() {     var scope = "local scope";        // 區域性變數     function nested() {         var scope = "nested scope";   //巢狀作用域內的區域性變數          return scope;                // 返回當前作用域內的值     }     return nested(); } checkscope();                         // => "nested scope"  
  • 作用域鏈(scope chain)
js的作用域鏈不是通過堆疊實現的,是通過維護一個連結串列來實現的。(忘記這句話在書的哪裡看到的了) 每一段JavaScript程式碼(全域性程式碼或函式)都有一個與之關聯的作用域鏈(scope chain)。這個作用域鏈是一個物件列表或者連結串列,這組物件定義了這段程式碼“作用域中”的變數。 連結串列的順序像是呼叫棧,連結串列的第一個物件是當前被呼叫的程式碼,連結串列的下一個物件指向呼叫“當前被呼叫的程式碼”的程式碼,這樣連續形成一個連結串列,連結串列的最後一個物件是全域性程式碼。 全域性程式碼也有一個全域性作用域,每一個被呼叫的函式都有一個作用域鏈,作用域鏈最後一個物件就是全域性作用域。 假設js需要查詢變數x的值的時候,它會從連結串列的第一個物件開始查詢,第一個物件是當前呼叫物件,如果找到名為x的屬性,則會直接使用這個x的值,因此區域性變數優先順序高。如果作用域鏈的第一個物件沒有名為x的屬性,則會查詢第二個物件,如果第二個物件依然沒有找x,則會繼續查詢下一個,直到最後一個全域性物件,如果全域性物件依然沒有找到x,則這段程式碼的作用域鏈上不存在x,那麼,程式就會丟擲一個錯誤(RefrenceError)異常 : Uncaught ReferenceError: x is not defined 注意:在JavaScript中,作用域鏈上找不到物件會丟擲(RefrenceError)異常,而原型鏈上找不到屬性並不會報異常,會得到undefined   var x = 1;                   // 定義全域性變數x var y = 2;                   // 定義全域性變數y function f1() {     var y = 100;             // 定義區域性變數y     var z = 200              // 定義區域性變數z       function f2(){         var y = 500;         // 定義區域性變數y         console.log(x);      // => 1         console.log(y);      // => 500         console.log(z);      // => 200     }     f2(); } f1(); 當js執行全域性程式碼的時候,作用域鏈只有一個物件:global,這時候找不到z,因為全域性作用域上只有屬性x和y。(x=1;y=2) 當js執行函式f1的時候,作用域鏈有2個物件:f1->Global,這時候f1的作用域在作用域的最前面,所以f1的作用域中的物件y會被優先讀取。(x=1;y=100;z=200) 當js執行函式f2的時候,作用域鏈有3個物件:f2->f1->Global,這時候f2的作用域在作用域鏈的最前面,所以f2的作用域中的物件y會被優先讀取。(x=1;y=500;z=200)    
  • 閉包(Closure)(8.6節)
普通的函式作用域內的變數,在作用域外不能被訪問。但有一種技術可以在函式作用域外訪問該作用域內的變數。   閉包:是一種實現詞法作用域的名稱繫結的一種技術。閉包儲存了函式和環境(比如作用域鏈),通過閉包儲存的值,可以訪問到閉包捕獲的那些變數。 JavaScript採用詞法作用域(lexical scoping),作用域是在函式定義時決定的(@@預編譯和函式定義),而不是函式呼叫時決定的。為了實現這種詞法作用域,JavaScript的函式物件不僅包含了邏輯程式碼,每個JavaScript函式物件還引用了一個作用域鏈。 在JavaScript中,函式也是一個值,而每個函式物件都關聯了一個作用域鏈,而函式體內部的變數都被儲存在函式作用域鏈內。所以從技術上來講,每個JavaScript函式都是一個閉包,因為每個函式都是一個物件(JavaScript中的函式也是一個物件),並且每個函式都關聯了一個作用域鏈。 想一下,如果我們將函式(閉包)傳遞到其他程式碼中,當其他程式碼呼叫這個閉包時,閉包所儲存的作用域鏈和當前程式碼不是同一個作用域鏈時,那句非常有趣了——我們可以在一個環境中(作用域鏈)訪問另一個環境(作用域鏈)的變數。通常我們將一個函式巢狀一個函式,外部函式將巢狀函式物件作為值返回,這時候我們就可以在函式外部訪問函式內部的變量了,因為這個被返回的函式值就是一個閉包,所以我們可以通過這個閉包訪問這個“被巢狀函式”的作用域鏈。 var scope = "global scope";         // 定義一個全域性變數 function checkscope(){     var scope = "local scope";      // 定義一個區域性變數     function f(){ return scope; }   // 定義一個巢狀函式,每個函式都關聯了一個作用域鏈     return f();                     // 返回一個閉包 } checkscope();                       // => "locall scope"  *輸出閉包內的值  
  •