好文推薦:我知道你懂 hoisting,可是你了解到多深?
什麼是 hoisting(提升)?
下面這樣寫,程式會出錯
console.log(b); // b is not defined
但是,改成下面這樣後,程式就可以正常執行了
console.log(b); // undefined
var b = 10;
上面的程式碼,可以想成是這樣:把「變數的宣告」提升到第一行
變數的宣告會 hoisting
注意,只有「宣告」會 hoisting,「賦值」不會 hoisting
var b;
console.log(b);
b = 10;
function 的宣告也會 hoisting
test();
function test() {
console.log(123); // 123
}
上面的程式碼,可以想成是這樣:把「function 的宣告」提升到第一行
function test() {
console.log(123); // 123
}
test();
hoisting 需注意的地方
注意,下面這樣寫,程式碼會出現錯誤:test is not a function
test();
var test = function () {
console.log(123);
};
原因為:
只有「變數的宣告」會 hoisting,「變數的賦值」不會 hoisting
所以,上面的程式碼可以看成是這樣:
在呼叫 test()
時,test
是 undefined,並不是一個 function,所以會出錯(test is not a function)
var test; // 變數的宣告
test(); // 這時的 test 是 undefined
test = function () { // 變數的賦值
console.log(123);
};
hoisting 只會發生在「變數自己的 scope 裡面」
因為 hoisting 是跟變數有關的,所以 hoisting 只會發生在「變數自己的 scope 裡面」
舉例:
var a = "global";
function test() {
console.log(a); // undefined
var a = "local";
}
test();
console.log(a)
會印出 undefined 是因為:
在 test scope 裡面,var a = "local"
這行會有 hoisting,可以看成是這樣:
var a = "global";
function test() {
var a; // 「變數的宣告」會 hoisting
console.log(a); // undefined
a = "local"; // 「變數的賦值」不會 hoisting
}
test();
hoisting 的順序:function > arguments > var
在 hoisting 時,優先權是 function > 變數
同時宣告了一個 function a
和變數 a
,這時,function 會有 hoisting 的優先權
function test() {
console.log(a); // [Function: a]
function a() {}
var a = "local";
}
test();
上面這段程式碼可以看成是這樣:
function test() {
function a() {} // function 會有 hoisting 的優先權
console.log(a); // [Function: a]
a = "local";
}
test();
同時宣告兩個同名的 function 時,後者會覆蓋掉前者
function test() {
console.log(a); // [Function: a]
a(); // 2
function a() {
console.log(1);
}
function a() {
console.log(2);
}
var a = "local";
}
test();
參數的 hoisting 順序
「參數」會優先於「宣告變數」
下面的 console.log(a)
會是 123,不會是 undefined
function test(a) {
console.log(a); // 123
var a = 456;
}
test(123);
就算是把上面的程式碼看成這樣:
就算 var a = 456
有 hoisting,但是如果 a
沒有重新賦值,a
的值就會是我傳進去的參數 123
下面的 var a
這句的意思只是「我要宣告一個變數 a
」,但是因為在 test scope 裡面已經存在變數 a
了,所以 var a
這句就會被忽略(並不會把 a
的值變成 undefined)
function test(a) {
var a;
console.log(a); // 123
a = 456;
console.log(a); // 456
}
test(123);
再舉另一個例子:
var a
這句會被忽略,因為在 test scope 本來就已經存在變數 a
了
function test(a) {
var a = "test";
console.log(a); // test
var a; // 我要宣告一個變數 a (會被忽略,因為本來就已經有變數 a 了)
}
test();
「function」會優先於「參數」
function test(a) {
console.log(a); // [Function: a]
function a() {}
}
test(123);
hoisting 的原理為何?從 ECMAScript 下手
Execution Contexts(簡稱 EC)就是「執行環境」
作用域和 hoisting 的小測驗
請問 log 出來的值依序會是什麼?
var a = 1;
function test() {
console.log("1.", a); // undefined
var a = 7;
console.log("2.", a); // 7
a++; // a = 8
var a; // 忽略
inner();
console.log("4.", a); // 30
function inner() {
console.log("3.", a); // 8
a = 30; // 這個 a 是「存活在 test scope 的區域變數」
b = 200; // b 變成全域變數了
}
}
test();
console.log("5.", a); // 1 (這個 a 是全域變數的 a)
a = 70;
console.log("6.", a); // 70
console.log("7.", b); // 200
output:
1. undefined
2. 7
3. 8
4. 30
5. 1
6. 70
7. 200
let 與 const 的詭異行為
如果是用 var
宣告變數
因為 hoisting,console.log(a)
會是 undefined
console.log(a); // undefined
var a = 10;
如果是用 let
和 const
宣告變數
console.log(a)
都會出錯,顯示「ReferenceError: Cannot access 'a' before initialization」
console.log(a);
let a = 10;
console.log(a);
const a = 10;
let
和 const
是有 hoisting 的
因為如果沒有 hoisting 的話,那在 function test
裡面的 a
,應該會往上層找到 let a = 10
,但是 console.log(a)
的結果並不是 10,是出現了錯誤「ReferenceError: Cannot access ‘a’ before initialization」
所以 let
和 const
是有 hoisting 的,只是 hoisting 的方式跟 var
不同(底層的運作方式不同)
let a = 10;
function test() {
console.log(a); // Cannot access 'a' before initialization
let a = 30;
}
test();
let
和 const
的 hoisting 運作方式,請接著往下看
TDZ:Temporal Dead Zone
let
和 const
的 hoisting 運作方式:不能在「賦值」之前,去存取變數
上面那段程式碼,的確是可以看成下面這樣(hoisting):
如果第三行是寫 var a;
,a
會被初始化成 undefined,所以 console.log(a)
就會是 undefined
但是,如果是 let a;
或是 const a
,變數都不會被初始化成 undefined
而且,直到「執行到第五行 a = 30;
之前」,我都不能去存取 a
從「進入 function」(hoisting)開始,一直到「賦值給變數」之前的這段區間,就是這個變數的 Temporal Dead Zone
以下面範例來說,Temporal Dead Zone 就是從「第三行開始到第五行」之間
- 在 Temporal Dead Zone 裡面,都不能去存取變數的值。如果存取的話,就會出現錯誤
let a = 10;
function test() {
let a;
console.log(a);
a = 30;
}
test();