在 ES6 之前,唯一產生作用域 (Scope) 的方法就是 函式(function);在 ES6 後,則多了一種方式 區塊(Block)。以函式來說,每一個函式內部有自己的作用域,出了作用域就無法存取這個函式內部所定義的變數;而區塊則是可以使用{...}
定義區塊範圍,以 const 和 let 宣告區塊內作用域的變數。
說到這裡就要問,什麼是作用域 (Scope) ?
作用域規定了如何去找尋變量,也就是確定當前取得變量的權限
Javascript 採取 靜態作用域(lexical scope)
變數的作用在語法解析的時候,就已經先確定作用域了,而且不會再改變
// global scope
function getUserId() {
// function scope
var user_id = '111';
return function () {
// function scope
console.log(user_id);
}
}
let showUserId = getUserId()
function getUser() {
// function scope
var user_id = '999';
showUserId()
}
console.log(showUserId()) // 111
getUser() // 111
當程式執行到getUserId
內部的 console.log(user_id)
時,無論此作用域是怎麼調用,都是通過向上找到var user_id = '111'
而獲取 user_id 的值。所以在函數還未執行前,就確定可以根據 static scope找到變量相對應的值,而這就是靜態作用域。
function ScopeTest(a) {
var b = 4;
function bar() {
console.log(`a:${a},b:${b},c:${c}`); // "a:3,b:4,c:5"
}
var c = 5;
console.log(a); // 3
console.log(b); // 4
console.log(c); // 5
bar();
}
ScopeTest(3);
console.log(a); // ReferenceError
在全域的狀況下,這裡會發現,此時是無法存取 a 這個在functionScopeTest裡的變數的;可是 bar 卻可以存取 functionScopeTest裡的 a, b, c變數,這是因為他們都存在在同一個 function 裡 = 同一個作用域的原因。
簡而言之,就是內層可以存取到外層的東西,但是外層存不到內層的。
在 ES6 出現之前,JS並沒有很嚴謹的定義 block scope,除了不推薦使用的 eval 和 with,大概只有 try/catch 的 catch 擁有區塊的概念。而 ES6 的發布明確帶來兩個新的變量:let 和 const,而這是推薦作用於 區塊作用域 的。
// global scope
function showUserId() {
// function scope
var user_id = '111'
if (true) {
// block scope
let user_id_2 = '999'
const user_id_3 = '777'
console.log(user_id_3) // 777
}
console.log(user_id) // 111
console.log(user_id_2) // ReferenceError
}
showUserId()
在區塊之外,可以發現 console.log
是無法存取 user_id_2
和 user_id_3
的,而就是 區塊作用域 的限制。
順帶一提,Javascript 是一個會自帶垃圾回收(Garbage collection)機制的語言,一般來說 JS 會自動幫我們處理好,但是我們也可以透過 區塊作用域 提升垃圾回收的效率。
雖然我們在第二天已經有稍微說明 var 在 function 作用域上的問題,但今天就讓我們講的再更詳細一點吧?
for(var i = 0; i < 10; i++){
setTimeout(function(){
console.log(i);
},100);
} // 10,10,10,10,......
for(let i = 0; i < 10; i++){
setTimeout(function(){
console.log(i);
},100);
} // 1,2,3 4,5,6...
這裡說明了 var
跟 let
是不同的,var
聲明變量會提升,是基於 global scope 或 function scope 中的,在程式執行setTimeout
的時候for
運行已經結束了,所以值都會是 10。而 let
是基於 block scope的,每次循環 i
只在當前的 block 有效,所以值是正確的。
預計會在補入閉包跟 hoisting 概念
JS 作用域
我知道你懂 hoisting,可是你了解到多深?