iT邦幫忙

8

JS Scope / Hoisting

js

JS Scope / Hoisting

筆記性質
建議搭配服用 : JS 執行環境
菜鳥初學,為了避免誤人子弟,請大神留步勘誤 QQ

範例

先來個範例,猜猜結果為何?

  var foo = 1;
  function bar() {
    console.log('bar First',foo)
    if (!foo) { var foo = 10; }
    console.log('bar Last',foo);
  }
  bar();
bar First: undefined
bar Last:  10

題目解析

  1. 執行 bar() 建立執行環境
  2. 創建階段: 將 var foo 提升至最上,但不會給予值 (此時為undefined)
  3. 執行階段: log('first', foo) 得到 undefined
  4. foo 為 undefined (falsy) !fasle得到 true
  5. 因此可執行 if 內容 給予 foo 值
  6. log('last',foo) 得到10
  var foo = 1;
  function bar() {
      var foo;       // 這裡改了
      console.log('bar First',foo) // undefined
      if (!foo) {
          foo = 10;  // 這裡改了
      }
      console.log('bar Last',foo); // 10
  }
  bar();

Scope

  • ES5 為 函數級作用域(function-level scope)

  • ES5 (var) Scope 切割單位為 Function

  • 因此 ES5 需要用 IIFE 來產生區域的 scope

  • ES6 以 {} 做劃分,使用 let / const 即可 (Blocak-level scope)

  • Scope Chain 範圍鏈
    找尋變量的過程,由內往外找,直到全域環境。

  • 注意 若在 Function 內 沒有用 var 重新宣告,會產生全域變數
    ,進而變成改寫全域變數中的 myVar

  • Outer Environment 外部環境
    當該執行環境找不到該變量時,會往外部環境做參照

  • 要如何區分 Outer Environment (外部環境) : 看程式碼的相對位置

var myVar = 'Global'
function a() {
  var myVar = 'Local'
  function b() {
    // b 的外層為 a()
    console.log(myVar) // Local
  }
  return b
}
// c 的外層為 Global
function c() { 
  console.log(myVar) // Global
}
a()()
c()
小測試
  var x  = 1
  if (true) {
    var x = 2
    let y = 2
    console.log(x)  // ? a
    console.log(y)  // ? b
  }
  console.log(x)  // ? c
  console.log(y)  // ? d
  • Ans
    a : 2 / b : 2 / c : 2 / d : y is not defined

  • ES5 var Scope 切割單位為 Function
    因此 if else 中的 var x = 2 等同於 重新宣告了 x

Hoisting 觀念

  • 使用 var 時,不論是否被執行,變數宣告皆會被提升至環境的最上方
    執行環境的建立階段,就已經將變數宣告加入該環境當中了,
    即 執行環境的建立階段,已進行Hoisting了

函式的 Hoisting

  • 函式 宣告式 會整塊程式碼被 Hoisting,因此可於宣告前使用
  • 函式 表達式 則只有變數宣告的部分被 Hoisting,
    因此於宣告前呼叫,會回傳 undefined,執行到該行時,才會進行給予值
foo() // foo is not a function
baz() // baz is not a function

bar() // 可用,函式宣告式,整塊被提升
function bar() = {}

var foo = function () {}; // 匿名函式表達式 (只有foo被提升)

var baz = function spam() { // 命名函式表達式 (只有baz被提升)
    // spam 變數名稱 只可以用於此區塊
    console.log(spam) // 指向此 Function
}; 

// Scope Chain 只可往外找,無法往內找
spam(); // ReferenceError "spam is not defined"

// IIFE 同 命名函式表達式 概念
;(function bzz() {
  var inSide = 123
  console.log(bzz) // 指向此 Function
}())
// IIFE 為 Function , Scope Chain 只可往外找,無法往內找
console.log(inSide) // inSide is not defined
console.log(bzz) // bzz is not defined

練習題

function b() {
    console.log(myVar) // a
    var myVar
    console.log(myVar) // b
}
function a() {
    var myVar = 2
    b()
    console.log(myVar) // c
    function d() {
      console.log(myVar) // d
    }
    d()
}
var myVar = 1
console.log(myVar) // e
a()
  • Ans
    a : undefined / b : undefined / c : 2 / d : 2 / e : 1

  • a 因為 myVar 會在 b 中 hoisting 因此不會取到外部變數,
    而是取到 var myVar; 的 undefined 值

總結 重要重點

  • ES5 Scope 切割單位為 Function

  • ES6 let const 為 Block Scope 因此只需要以 {} 劃分

  • 使用 var 時,不論是否被執行,變數宣告皆會被提升至環境的最上方

  • Hoisting 於 建立 執行環境 的 建立階段 執行 (複習 JS 執行環境)

  • 只會 Hoisting 宣告的部分,賦値仍要在執行階段執行。

  • 函式宣告式 : 會 Hoisting 整塊程式碼,因此可於宣告前使用

  • 函式表達式 : 只會提升宣告的部分,賦値仍要在執行階段執行。

參考資料

andyyou
DavidShariff
PJChENder
ben cherry


0

感謝大大分享,第一次聽到 變數命名優先序
以前都以為是單純的後壓前,原來是有優先序的。
/images/emoticon/emoticon32.gif

RocMark iT邦新手 5 級 ‧ 2018-06-02 23:05:26 檢舉

那個部分是我自己去測試的,有可能會漏掉一些細節順序,有抓到漏洞的話歡迎告知。
實際寫Code,當然是避免取相同變數為妙啦!

1
fillano
iT邦超人 1 級 ‧ 2018-06-05 15:59:35

補充一下:

第一個例子裡的console.log('bar First',foo)這個狀況,叫做Temporal Dead Zone,簡寫成TMZ。

RocMark iT邦新手 5 級 ‧ 2018-06-05 16:03:41 檢舉

感謝補充 ~
沒看過這個名詞,來去補補相關知識 QQ
簡寫好像是 TDZ?

fillano iT邦超人 1 級 ‧ 2018-06-06 09:23:26 檢舉

阿,是TDZ....Orz

0

感謝版主賜教

var x  = 1
  if (true) {
    var x = 2
    let y = 2
    console.log(x)  // ? a
    console.log(y)  // ? b
  }
  console.log(x)  // ? c
  console.log(x)  // d =>這邊是不是console.log(y)?

Ans
a : 2 / b : 2 / c : 1 / d : y is not defined

RocMark iT邦新手 5 級 ‧ 2018-06-14 16:10:38 檢舉

感謝勘誤 !
c 會是 2 因為 if 中 var x = 2 會重新宣告 全域變數 x
(ES5 var scope 最小範圍為 Function)

我要留言

立即登入留言