iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
1

如果函數在自己的執行環境裡的變數環境中找不到變數的值會往外部環境找,
外部環境會參照到函數本身的詞彙環境(程式碼寫在哪裡),如果找不到會再往範圍鏈上去找,直到全域執行環境,

我們用一個範例來看:

function b() { // 函數 b 的位置(程式碼的實際位置(詞彙環境)在全域執行環境,所以會參考到全域執行環境的變數環境)
console.log(myVar); // 1
}
function a() {
var myVar = 2;
b();
}
var myVar = 1;
a();

我們在函數 b 中沒有宣告 myVar
猜猜現在 myVar console.log 出來的值是多少?

我們到 Chrome 的 Console 來看:

你可能會認為在 a 函數裡呼叫 b 函數,結果應該是 2,
但不是,結果是 1,

還記得每個執行環境在創造階段都會替我們產生特殊變數 this 以及外部環境,
外部環境會參照到函數本身的詞彙環境,
這邊因為 b 函數被呼叫時執行環境被丟進執行堆最上方,但在自己的變數環境裡找不到 myVar ,因為函數 b 的詞彙環境(程式碼中的實際位置)在全域環境(跟 var myVar = 1; 都在全域執行環境),所以參考的外部環境參照到全域執行環境,在全域執行環境的變數環境 myVar 的值是 1 ,所以函數 b 中 console.log myVar 的值為 1,


圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 16 影片截圖

這整條鏈子叫做範圍鏈,
記住「範圍」代表我能夠取用這變數的地方,
而「鏈」是外部環境參照的詞彙環境,

現在如果我們調整函數 b 的位置,將函數 b 調整到函數 a 裡面,
程式碼如下:

function a() {
  function b() {  // 函數 b 的外部環境參照到函數 a 的執行環境 ,所以會先參考函數 a 裡的 myVar
    console.log(myVar); // 2
  }
  var myVar = 2;
  b();
}
var myVar = 1;
a();

這時候結果為何?
我們一樣在 Chrome 的 Console 中來看:

可以看到現在 myVar console.log 出來的值是 2,

因為 b 函數的詞彙環境在 a 函數裡,
所以 b 函數的外部環境參照的詞彙環境是 a 函數,
a 函數的執行環境中的變數環境裡 myVar 的值是 2,所以現在我們 console.log 出來的 myVar 的值為 2,
有沒有跟你想的一樣?


圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 16 影片截圖

我們試著在全域執行環境呼叫函數 b,
程式碼如下:

function a() {
  
  function b() {
    console.log(myVar);
  }

  b();
}

var myVar = 1;
a();
b();

想一下在全域執行環境呼叫函數 b的結果為何?
我們到 Chrome 的 Console 來看:

因為函數 b 在函數 a 裡,
當全域執行環境被創造時只有看到函數 a,
函數 b還沒被設定到記憶體空間裡,
所以在全域執行環境呼叫函數 b 時會出現參照不到的 b is not defined 錯誤,

那如果我們再把 a 函數裡的 myVar 拿掉,
程式碼如下:
:

function a() {
b();
  function b() {
    console.log(myVar);  // console 出來的值為1
  }
}
var myVar = 1;
a();

現在 myVar console.log 出來的值是多少?
來看一下 Console 中的結果:

可以看到 myVar console.log 出來的值為 1,
因為函數 b 的外部環境參照到的詞彙環境為函數 a ,
函數 a 的執行環境裡的變數環境找不到 myVar ,
所以會繼續到範圍鏈上去找(直到全域執行環境) myVar 變數,


圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 16 影片截圖

也有另外一種理解的方式,
可以先想函數的執行環境是在哪裡被創造,
外部環境會先參考函數的執行環境在哪被創造,

但用詞彙環境來理解會比較清楚。


上一篇
Day 6 函數呼叫與執行堆及變數環境
下一篇
Day 8 關於非同步回呼
系列文
教練我想學 JavaScript 30

尚未有邦友留言

立即登入留言