iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 4
4
Modern Web

從0.5開始的JavaScript系列 第 4

Day4 Hoisting & Scope Chain

前言都不知道要打什麼,就直接了當吧,今天要來簡單聊聊 HoistingScope Chain

本文開始


Hoisting

我們先來看這段程式碼

console.log(a);
var a = 123;

JS 是同步執行,也代表程式碼會由上而下一行一行執行,那上面的範例中,我們在宣告 a 之前就試圖印出它,這樣應該會顯示 a is not defined 吧!

但人參就是這樣,總是會有一些意料之外的事發生

執行結果是 undefined(有宣告,但沒有值),而不是 not defined(沒有宣告)。
這個結果在 function 也是一樣,可以在宣告式上面呼叫(執行)它。

該如何解釋這個現象呢? 這是今天要講的主題之一 hoisting

JS 在執行時大致分為兩個階段

  1. 創建
    • 編譯(解析)程式
    • 創建全域執行環境
    • 變數 this
    • 創建全域物件(在瀏覽器中為 window)
    • 將變數、函數放入記憶體
  2. 執行

在第一個環節中,所宣告的變數、函數就會被放到記憶體,所以才沒有出現 not defined 的錯誤。
流程轉為程式碼可以理解成這樣:

var a;
console.log(a);
a = 123;

在我們定義變數的過程中,可以分成宣告給值的兩個過程,宣告的變數會在逐行執行程式前,先被執行並儲存在記憶體(hoist),給值的內容則是在 hoist 之後,逐行執行程式時,才會被執行。
所以程式一開始執行的時候,就已經把 var a宣告存在記憶體中了,但是還沒把值賦予給 a 這個變數,所以 a 在印出時 undefined 的結果。

But

記得昨天講的 ES6 新增的兩個變數宣告方式嗎,let & const
它們提供了更嚴謹的撰寫方式,其中有一個特性就是禁止在宣告前使用

Scope Chain

延續昨天的變數作用範圍,接下來要提提 Scope Chain
用 var 所宣告的變數,作用範圍是在當時所在環境(函數內),而不使用 var 直接指定值而建立的變數,則是全域物件(window)上的一個屬性,也就全域範圍。

在 JS 中有稱作 Scope Chain(範圍鏈)的特性,JS 在使用變數時,會遵循著 Scope Chain 一層一層往外找,若函數內找不到,則往外找。

這個很好理解,func 內沒有 myVar 這個變數,所以向外一層去找看看有沒有這個變數,而它也找到了。

var myVar = "outer";
function func(){
  console.log(myVar); // outer
  myVar = "inner";
}
func();
console.log(myVar); // inner

那這個呢,func 中的 myVar 會印出什麼?

function func(){
  console.log(myVar); // ?
}
function fund(){
  var myVar = "inner";
  func();
}
fund();
var myVar = "outer";

答案是 func 裡面印出的 myVarouter 而不是 inner

這邊要補充一下,JS 屬於同步執行,所以執行 function 時,會依執行順序把 function 丟到 stack 中,而每個 function 在執行時,都會建立屬於它自己的執行環境,也就是環境中的變數是獨立存在該環境中,不會互相汙染。

看看這段程式碼,Q1Q2=?

var myVar = "outer";
function func(){
  var myVar = "inner";
  console.log(myVar); // Q1?
}
func();
console.log(myVar); // Q2?

聰明的你應該答對了,
Q1: inner
Q2: outer

儘管在 func 外面已經宣告 myVar,而在 func 又宣告了 myVar,這兩個變數看起來是一樣的,但其實不然。
func 在執行時會產生新的執行環境,而其宣告的 var myVar = "inner" 只存在於這個新產生的執行環境,和外部(全域)的 var myVar = "outer" 八竿子打不著。

回到最開始的程式碼

var myVar = "outer";
function func(){
  console.log(myVar); // outer
  myVar = "inner";
}
func();
console.log(myVar); // inner

雖然 myVar 是屬於外部(全域)的變數,它並不存在(活動)於 func 內,func 中之所以能夠印出它的值,是因為 Scope Chain 的特性,當內層的環境找不到這個變數,就會往外去找。

理解之後看看這段程式碼,請問 Q3 會印出什麼呢?

function func(){
  var myVar = "inner";
}
func();
console.log(myVar); // Q3=?

答案是 myVar is not defined
因為 Scope Chain 的特性是由內往外找,並不會也不能往內找。所以無法使用函數 func 內的變數 myVar

注意:內層函式都可以存取外部函式的變數;但外部不能存取內部

多層函數也是遵循一樣的規則,Q4=?

var myVar = "global";
function outer(){
  var myVar = "outer";
  function inner(){
    console.log(myVar); // Q4=?
  }
  inner();
}
outer();

Ans:
Q4 = outer
function inner 的執行環境找不到 myVar,所以向外找到了 outer 中的 myVar,因為找到了,所以不會再往外去找到 myVar = "global"


那今日的分享就到這邊,明天見/images/emoticon/emoticon51.gif


上一篇
Day3 Variable Scope
下一篇
Day5 單執行緒&非同步發生的血案
系列文
從0.5開始的JavaScript30

尚未有邦友留言

立即登入留言