中阶魔法 - 范围链 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/


<<:  【20】从头自己建一个 keras 内建模型 (以 MobileNetV2 为例)

>>:  RDS Deadlock

灵异现象 - 怎麽大家都能改阿

灵异现象 - 怎麽大家都能改阿 灵异现象 故事主角:小新 小新的 IT 同事小王最近转部门跑去做 R...

成员 9 人:在办公室内,建立时空虫洞

根据相对论,如果一对挛生兄弟, 哥哥搭乘宇宙飞船,以接近光速,飞离地球,在宇宙间航行; 当回到地球的...

Ruby解题分享--Sqrt(x),二分搜寻演算法。

老歌了~ 宅男开YouTube来看,永远不缺手游广告... 最近有个广告台词,开局一定要选拳法,如...

Day21 - 【概念篇】在Flow这段小旅途外的风景

本系列文之後也会置於个人网站 在这一小段路中介绍了Password Flow、Implicit F...

[面试][後端]请简述 Node.js 的 Event Loop

熟悉的起手式:「我方便问你一个 Node.js 核心的问题吗?」 这是一个在了解後,无论面试还是工...