1. 程式人生 > >javascript系列 ————詞法作用域、作用域鏈(二)

javascript系列 ————詞法作用域、作用域鏈(二)

詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫程式碼時將 變數和塊作用域寫在哪裡來決定的,因此當詞法分析器處理程式碼時會保持作用域不變(大部分情 況下是這樣的)。

                                                                                                   _____________《你不知道的javascript》

目錄

作用域

什麼是作用域

塊級作用域:即塊級的作用範圍

在js中詞法作用域規則:

作用域鏈結構

繪製作用域鏈

變數的訪問(搜尋)規則

分析程式碼


作用域

"詞法作用域是作用域的一種工作模型",通俗的講“沒有作用域的概念就沒有詞法作用域的概念。”..

什麼是作用域

表示的就是範圍,即作用範圍,就是一個名字在什麼地方可以別使用,什麼時候不能使用。

作用域就是一套規則,用於確定在何處以及如何查詢變數(識別符號)的規則,通俗的講,作用域就是查詢變數的地方。在某函式中找到該變數,就可以說在該函式作用域中找到了該變數;在全域性中找到該變數,就可以說在全域性作用域中找到了該變數!

先看一段及其簡單的程式碼:

function foo(){
    var a=123;
    console.log(a);//result 123
}
foo()

在foo函式執行的時候,輸出一個a變數,那麼這個a變數是哪裡來的嘞,有看到函式第一行有定義a變數的程式碼var a = '123' 

再看下面這段程式碼: 

var num=123;
function foo(){
    console.log(num);//result 123
}
foo()

同樣的道理:在輸出num的時候,在自己函式體內沒有找到num,那麼就會到函式外層的全域性中查詢,找到了就停止查詢並輸出了。

注意以上兩段程式碼都有查詢變數,第一段程式碼是在函式中找到a變數,第二段程式碼是在全域性中找到b變數。函式作用域全域性作用域,把這兩個詞換入到原來那句話中,第一段程式碼是在函式作用域中找到a變數,第二段程式碼是在全域性作用域中找到num變數。通俗的講,作用域就是查詢變數的地方。在某函式中找到該變數,就可以說在該函式作用域中找到了該變數;在全域性中找到該變數,就可以說在全域性作用域中找到了該變數!

塊級作用域:即塊級的作用範圍

JavaScript沒有塊級作用域(一對花括號{}即為一個塊級作用域),只有全域性作用域和函式作用域。

而在C、Java、C#等程式語言中,下面的語法報錯(虛擬碼)

{
    var numb = 123;
    {
        console.log( numb );    // num => 123
     }
}
console.log( numb );   //報錯

在js 中採用詞法作用域

所謂的詞法(程式碼)作用域,就是程式碼在編寫過程中體現出來的作用範圍,程式碼一旦寫好,不用執行,作用範圍已經確定好了,這個就是所謂的詞法作用域

在js中詞法作用域規則:

  1. 函式允許訪問函式外的資料,
  2. 這個程式碼結構中只有函式可以限定作用域。
  3. 作用規則首先使用提升規則分析
  4. 如果當前作用規則中有名字了,就不考慮外面的名字。

例子1:

var num=123;
function foo(){
    console.log(num);
}
foo()//result 123

例子2:

 if(false){
     var num=123;
}
console.log(num);//undefined

例子3:

var num=123;
function foo(){
    var num=456;
    function func(){
        console.log(num);//456
    }
    func();
}
foo();

作用域鏈結構

可以發現只函式才能製作作用域結構,那麼只要是程式碼,至少有一個作用域,即全域性作用域。

凡是程式碼中有函式,那麼這個函式就構成另一個作用域。如果函式中還有函式,那麼在這個作用域中就又可以誕生一個作用域,那麼將這樣的所有作用域列出來,可以有一個結構:函式內指向函式外的鏈式結構

例如:

function f1(){
   function f2(){
   }
}
var num=456;
function f3(){
   function f4(){
   }
}

作用域鏈結構與DOM樹結構很相似。

繪製作用域鏈

步驟:

  1. 看整個全域性是一條鏈,即頂級鏈,記為0級鏈
  2. 看全域性作用域中有什麼成員宣告,就以方格的形式繪製到0級鏈上
  3. 再找函式,只有函式可以限制作用域,因此從函式中引出新鏈,標記為1級鏈
  4. 然後再每一個1級鏈中在此往復剛才的行為。

變數的訪問(搜尋)規則

  1. 首先看變數在第幾條鏈上,在該鏈上看是否有變數的定義與賦值,如果有直接使用
  2. 如果沒有就到上一級鏈上找(n-1級鏈),如果有直接使用,停止繼續查詢。
  3. 如果還沒有在此往上找……直到全域性鏈(0級),還沒有就是is not defined 
  4. 注意,切記:同級鏈不可混合查詢

繪製如下程式的作用域鏈

function f1(){
      var num=123;
      function f2(){
          console.log(num);
      }
      f2();
}
var num=456;
f1();//123

  1. 首先函式f1()和變數num=456,在0級鏈上
  2. 而f1下面又可以展開1級鏈,
  3. 1級鏈上有num=123 和 函式f2。
  4. 程式f1()呼叫進入左邊1級鏈,而f1中又呼叫了f2函式,f2函式中console.log(num)可以看作在2級鏈,
  5. 此時,程式會向這一條鏈向上查詢,首先2級鏈沒有num宣告,向上到達1級鏈,
  6. 剛好1級鏈上有num=123,所以就直接使用123,
  7. 程式最後的結果列印123,

分析程式碼

  1. 在分析程式碼的時候切記從程式碼執行角度上來分析,如果程式碼給變數賦值了,一定要標記到圖中;
  2. 如果程式碼比較複雜,可以在圖中表示程式碼的內容,有時候還要將原型圖與作用域圖結合起來分享。

分析程式碼如下:

var num=123;
function f1(){
    console.log(num);
}
function f2(){
    var num =456;
    f1();
}
f2();//123

作用域鏈圖繪製:

作用鏈圖預解析:

  1. 首先把num=123,函式f1,函式f2畫在0級鏈上。
  2. f1中只有一句console.log(num),畫出一條1級鏈,
  3. f2也畫出1級鏈,連上有num=456和函式呼叫語句f1();
  4. 呼叫f2(),進入f2函式的作用域鏈,而在f2中又呼叫了f1函式,
  5. 程式進入f1的作用鏈,所以console.log(num)會在此鏈上查詢是否存在num,
  6. 沒有,繼續向上一級查詢,剛好在0級鏈上找到了num=123,
  7. 所以f1函式中的console.log(num)列印就是123.

 

 

參考