iT邦幫忙

2021 iThome 鐵人賽

DAY 20
2
Modern Web

JavaScript 魔法入門 - 從入門到中階觀念系列 第 20

中階魔法 - 範圍鍊 Scope Chain

前情提要

上回與艾草玩遊戲輸了要接受處罰。

「都躲這麼遠了,她應該找不到我了吧!」

艾草:「啊哈,原來你躲在這裡呀!」

「你怎麼找到的?明明我跑超遠了呀。」

艾草:「範圍追蹤魔法呀,如果 1 km 內找不到你,那魔法就 5 km 、 10 km 的幫我找,就不信找不出你。」

「啊啊,這魔法也太好用了吧,快點教我吧!我一直在找尋很重要的東西。」

艾草:「可以呀!是要找什麼?」

「我遺失這些年的女朋友 (இ﹏இ 。)」


在開始範圍鍊 (Scope Chain)介紹之前,讓我們先了解作用域是什麼東西吧!

作用域

JavaScript 會在程式運行之前語法解析階段就決定作用域,稱為語法作用域(Lexical scope)

作用域的劃分

在先前的文章-變數宣告 letconstvar 有提到。

首先,我們先來談談 ES6 才新增的宣告方式 letconst, 與 var 不同的地方在 letconst 作用域在區塊,區塊指的是 {} 內,例如 iffor 迴圈等,而 var 的最小切分單位為 function

作用域的劃分大致可區分為:

  • 全域
  • 透過 var 宣告的函式作用域
  • 透過 letconst 宣告的塊狀作用域

以程式碼來舉例:

let myName = "艾草";
function sayHello() {
  var myName = "烙詩";
  if (true) {
    let myName = "筑茵";
    console.log(`塊狀作用域打招呼${myName}`);
  }
  console.log(`函式作用域打招呼${myName}`);
}
console.log(`全域打招呼${myName}`);
sayHello();

印出結果:
https://ithelp.ithome.com.tw/upload/images/20211004/20139066sjMQx3VkBZ.png

實際上的作用域劃分,會如下圖所示:

簡單來說 var 會被關在函式內, letconst 會被關在 {} 大括號內,外層取用不到,而全域則是誰都可以取用。

https://ithelp.ithome.com.tw/upload/images/20211004/20139066Z3C2Lcv6W5.png

外層作用域取不到內層作用域的值,但內層作用域如果找不到值,可以一直向外層作用域尋找,而這就是今天要介紹的範圍鍊 (Scope Chain)的觀念!


範圍鍊 (Scope Chain)

我們在撰寫 JavaScript 時,有時會宣告全域變數,而在函式作用域內也能去取用該全域變數,而範圍鍊便是指如果函式作用域內沒有找到指定變數時會向不斷向外尋找的過程,可以看到範例程式碼函式內並沒有宣告變數 ab ,卻能取用到:

let a = 1;
let b = 1;
function add() {
  console.log(a + b);//2
}
add();

如果在函式內呼叫另一個函式,那它會取到哪個變數呢?

另外定義了一個函式 test ,並於 test 內分別也宣告了變數 ab,並於 test 內呼叫 add 函式,會發現印出來的結果依然是 2 ,所以函式 add 依然取用到全域的變數 。

let a = 1;
let b = 1;
function add() {
  console.log(a + b);//2
}
function test() {
  let a = 3;
  let b = 4;
  add();
}
test();

因為函式的區域內沒有該變數,所以它們會向全域尋找變數,為什麼在 test 函式內呼叫 add 函式並沒有影響到 add 函式印出的結果呢?因為 JavaScipt 的作用域在一開始就決定了,並不受執行堆疊與何時調用的影響。

https://ithelp.ithome.com.tw/upload/images/20211004/20139066PCTefHxyXo.png

如果直接將 add 函式移進 test 函式內結果會一樣嗎?

let a = 1;
let b = 1;

function test() {
  let a = 3;
  let b = 4;
  function add() {
    console.log(a + b);//7
  }
  add();
}
test();

會發現印出來的結果與上方不同了,因為巢狀函式內層的 add 函式在自己的作用域內找不到變數時,會往外層的 test 函式作用域尋找,而在 test 函式就能找到它需要的變數了,所以並不會繼續全域環境查找!

總結

  • var 宣告的變數為函式作用域
  • letconst 宣告的變數為塊狀作用域
  • 範圍鍊是指作用域內沒有找到指定變數會向外尋找的過程
  • 範圍鍊的作用域一開始就決定了與執行堆疊跟調用方式無關

小練習

請問以下 console.log() 會印出什麼呢?

let a = 1;
let b = 1;
function test() {
  let a = 3;
  let b = 4;
  function add() {
		let a = 2;
	  let b = 2;
    console.log(a + b);//?
  }
  add();
}
test();

歡迎丟進開發人員工具檢視唷!


參考文獻

JavaScript 核心篇(六角學院)
https://codingbycolors.me/graphical_js_rules_scope/


上一篇
中階魔法 - 執行環境與執行堆疊
下一篇
中階魔法 - 傳值、傳參考
系列文
JavaScript 魔法入門 - 從入門到中階觀念30

尚未有邦友留言

立即登入留言