[JS奇怪的世界]No.10 範圍鏈

範圍鏈

接下來要講得是「範圍鏈」,而這觀念並不會太難,因為在前面已經練習很多次了,更不用說上一章其實有講到範圍鏈(The Scope Chain),一樣從課堂範例程式碼學習 ↓

1
2
3
4
5
6
7
8
9
10
11
function b() {
console.log(myVar);
}

function a() {
var myVar = 2;
b();
}

var myVar = 1;
a();

這邊要注意函數 b(),並沒有建立變數(var myVar),而是直接用 console.log 出來。

b

我們可以發現 myVar 竟然是 1?!原因是為什麼?首先我們思考一下函數在執行時的執行堆。

執行堆

我們可以看到函數 a() 與函數 b() 都是疊在全域環境物件上,這代表著建立在全域環境物件下的 var myVar = 1; 可以被其他的執行環境給呼叫及取用,除非你在函數中建立相同名稱的變數,這樣它才不會去取用全域環境物件下的 var myVar = 1;

用課程的圖來講就會像這樣子,執行時 b() 裡面並沒有 myVar

並沒有 myVar

而 JavaScript 很特別,它並不會只在預設執行環境中尋找變數,所以這邊要注意每一個執行環境下都會建立兩個東西

  • 全域環境物件
  • 變數 this

那課程也有講到 JavaScript 會參照外部環境,那什麼是外部環境?拿函數 b() 來講,它的外部環境就是全域執行環境,就如課程簡報一樣。

全域執行環境

所以 a() 也是一樣。

全域執行環境

而這章節有一個重點,我們要如何辨別它會**參照哪一個外部環境?**這邊就跟這「詞彙環境」有關係了,以函數 b() 來講它的詞彙環境會在哪裡?可以思考一下我們是在什麼環境下建立函數 b()

1
2
3
4
5
6
7
8
9
10
11
function b() {
console.log(myVar);
}

function a() {
var myVar = 2;
b();
}

var myVar = 1;
a();

從程式碼來看我們可以知道函數 b() 建立在全域環境,而不是 a(),所以 b()外部環境就是全域環境

那如果這樣子呢? b() 的詞彙環境又會在哪裡?而函數 b() 的外部環境又是誰? console.log(myVar); 又會是多少?

1
2
3
4
5
6
7
8
9
10
function a() {
var myVar = 2;
b();
function b() {
console.log(myVar);
}
}

var myVar = 1;
a();

只要搞到前面的觀念,相信我們已經知道答案了。

詞彙環境

但是這邊也有一個重點要注意,JavaScript 它很特別,它非常注重詞彙環境,所以當你某個執行環境內的變數它找不到的時候,它就會替改從外部環境找變數

而這整個過程就是範圍鏈(就像一條鏈子一樣)。

範圍鏈

所以當函數 b()myVar 找不到,就會往外部環境往下找,而這行為也就是所謂的範圍鏈那它並不一定直接找最後一個外部環境,而是要依照看詞彙環境來決定,就像我剛剛寫的範例一樣 ↓

1
2
3
4
5
6
7
8
9
10
function a() {
var myVar = 2;
b();
function b() {
console.log(myVar);
}
}

var myVar = 1;
a();

範圍鏈

此時它就會像課堂上的簡報一樣。

簡報

那這時候如果我這樣修改呢?

1
2
3
4
5
6
7
8
9
function a() {
b();
function b() {
console.log(myVar);
}
}

var myVar = 1;
a();

這時候 console.log(myVar); 會出現什麼?

結果

結果是 1,如果忘記的話沒關係,我們回頭看一下課程簡報 ↓

範圍鏈

因為變數 myVar 在函數 a() 找不到,所以它就又往外部環境的下一層找,結果找到了全域環境的 var myVar = 1; ,所以答案才會是 1

圖源

JavaScript 全攻略:克服 JS 奇怪的部分