2015-01-21

Effective Javascript #12 - 變數拉升 Variable Hoisting

又解了一個謎團,Javascript 的區域變數(Local Variable)是 Function-scoped,而非  Block-scoped。

用程式說明。
function hoist(i) {
  console.log('input - ' + i);
  for (var i=0; i<5; i++) {
    console.log('loop - ' + i);
  }
  console.log('return - ' + i);
  return i;
}
hoist(3);
傳入 i = 3,第一行輸出 input - 3,沒有問題。

然後進入迴圈,重新命名 i,從 0 加到 4,因而輸出 loop - 1 ~ 4,這也可以理解。

問題在 return 應該是多少?

按照 Block-scoped 概念,應該是 return - 3,錯!

Javascript 是 Function-scoped,所以得到 return - 5。
input - 3
loop - 0
loop - 1
loop - 2
loop - 3
loop - 4
return - 5
Javascript 對於變數定義的 scope,並不是找最接近的 statement 或者 block,而是最接近的 function。

因此,Javascript 會將上述的程式改寫成以下的方式。
function hoist(i) {
  console.log('input - ' + i);
  var i;
  for (i=0; i<5; i++) {
    console.log('loop - ' + i);
  }
  console.log('return - ' + i);
  return i;
}
將原本是 Block-scoped 的變數 i 向上拉生成 Function-scoped。

但有一個例外,就是在 catch 區裡的錯誤物件不會被拉升。
function hoistInError(e) {
  console.log('input - ' + e);
  try {
    throw 'Oh! NO!';
  }
  catch (e) {
    console.log('error - ' + e);
  }
  console.log('return - ' + e);
  return e;
}
hoistInError("I'm ok!");
得到以下的結果。
input - I'm ok!
error - Oh! NO!
return - I'm ok!
因此,Effective Javascript #12 建議,自己手動拉升可以避免程式閱讀困惑的產生。
---
---
---

2 則留言:

  1. 這個我在寫的時候沒注意過,基本上都是用不同的名字

    回覆刪除
  2. 作者已經移除這則留言。

    回覆刪除