FE 讀書會(一): JavaScript Scope
JavaScript Scope
變量作用域
JavaScript 有兩種變量作用域:
- Gloable Scope: 定義在
函數之外
的為全局變量,整個程序都可以訪問,直到頁面關閉才銷毀。 - Local Scope: 定義在
函數之內
為局部變量,函數執行開始到結束,會創建與銷毀該變量。
以下列出一些特性:
- 局部變量不影響全局變量:
var str = 'hello';
function test() {
var str = 'hi';
}
test();
str += ' R00.';
console.log(str) // print `hello R00.`
- 沒有宣告視為全局聲明:
console.log('hello :' + name) // print `hello Yuyu`
// 此處 function 其實就是一個全局的聲明
function test() {
name = "Yuyu";
}
console.log('hello :' + name) // print `hello Yuyu`
- 閉包將保存局部變量的引用:
function greet(name) {
return function () {
sayHello(name);
}
}
function sayHello(name) {
console.log('hello ' + name);
}
var func = greet('Yuyu');
func(); // print `hello Yuyu`
sayHello('Jay'); // print `hello Jay`
func(); // print `hello Yuyu`
- 變數的提升 hoisting:
var number = 100;
test();
function test() {
console.log(number); // print `undefined`
if (false) {
var number = 123; // never call
}
}
關於提升 hoisting 可以簡單的理解為
提前訪問
區塊作用域
區塊作用域 Block Scope 在很多語言都有,但是 JavaScript 沒有,直到 ES6 後才導入此特性。
var
與 let / const
的比較:
- var 為函數作用域
- let / const 為 區塊作用域
幾個經典的作用域例子:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
let i = 0
for (; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
- var 在聲明之前被訪問會拋出
undefined
- let / const 在聲明之前被訪問會拋出
ReferenceError
console.log(_var) // undefined
console.log(_let) // ReferenceError: _let is not defined
var _var = 1
let _let = 2
根據 ES6 標準規範 13.3.1:
The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.
A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
簡單是說由 let / const 聲明的變量,只有在詞法綁定 (LexicalBinding) 給值後,才能夠被訪問。 變量的初始化必須經過賦值,如果 let 在綁定過程沒給值,將以
undefined
當作初始值。
- 進一步我們可以觀察到 var 與 let 在聲明都有被 hoisting,都會先在作用域上被創建出來,但是 let 在詞法綁定之前被訪問的話會拋出錯誤。
- 在變量被創建到變量可以被訪問(初始化完成)的這一段時間稱之為 Temporal Dead Zone (TDZ)
提升 hoisting
是 JS 基本的特性,在有些地方會將 let / const 歸類到無法 hoisting,但其實不然,let / const 也是會受到提升,只是因為 TDZ 作用的關係,所以拋出錯誤ReferenceError
,跟 var 拋出undefined
有所不同。
Temporal Dead Zone (TDZ)
以下我們來看幾個 TDZ 常發生錯誤的例子:
let x = x
console.log(x)
function foo(x = y, y = 1) {
console.log(y)
}
foo(1) // good
foo(undefined, 1) // ReferenceError: `y is not defined`
foo() // ReferenceError: `y is not defined`
function f() { return x }
f() // ReferenceError
function f() { return x }
let x = 1
f() // nothing error
let x = 1
function foo(a = 1, b = function(){ x = 2 }){
let x = 3
b()
console.log(x)
}
foo()
console.log(x)
// babel Compiler: 2, 1
// Closure Compiler: 3, 2
// Google Chrome(v55): 3, 2
// Firefox(v50): 2, 1
// Edge(v38): 3, 2
function foo(a = 1, b = function(){ let x = 2 }){
b()
console.log(x)
}
foo()
// Chrome: ReferenceError: x is not defined
// Firefox: ReferenceError: x is not defined
雖然 ES6 制定了標準,但目前 TDZ 在作用域上各家瀏覽器實作略有不同,所以盡量避免在參數預設值上做有副作用的運算
Author Yuyu, Lin
LastMod 2018-05-07