Scope是什麼?
Scope就是變數的有效範圍(可以被取用的範圍),一旦超出範圍就無法存取,
可以分為幾種-全域範疇、函式範疇、區塊範疇
Global Scope 全域範疇:
全域範疇指的是最外層的範疇,精準來說是瀏覽器中BOM的window物件,
想了解更多瀏覽器知識,可以看看kuro大大的文章
重新認識 JavaScript: Day 11 前端工程師的主戰場:瀏覽器裡的 JavaScript
我們在全域範疇設的變數(全域變數),都會成為全域物件的屬性
所以我們也可以用 window. 去取得我們得變數
var a = 1; // 全域變數
console.log(window.a) // 1
全域範疇的有效範圍是全部,在代碼中的任何地方都可以取用。
Function Scope 函式範疇
每個函式都會有自己的範疇,有效範圍只在函式內部,外層無法直接訪問函式中的範疇,
並且函式結束後,裡面的變數就無法取得了。
function foo() {
var a =3
function test(){
console.log(a) // 3 在有效範圍中,順利取得
}
console.log(a) // 3 在有效範圍中,順利取得
test()
}
foo()
console.log(a) // a is not defined 超出範圍,取不到function內的變數
另外要注意的是,如果沒有用var宣告,就算在函式內也會被視為全域變數,
盡量不要這樣做,因為污染全域不是一個好主意。
Scope Chain 範圍鏈
插撥一個觀念,大家會發現上面的代碼中foo()裡面的test(),也取得到外層的a變數,
這其實就是Scope Chain(範圍鏈)的概念,當它在範疇內找不到變數時,會從自己的層級開始,一層層往上找,直到找到第一個符合的為止。
Block Scope 區塊範疇
在ES6以前,並沒有區塊範疇的概念,我們只能以函式為區分,
而從ES6開始,引入了Let、Const關鍵字,它們以區塊{}當範疇,如此一來我們就可以利用區塊範疇寫出更嚴謹,更好維護的代碼。
{
var a = 1;
let b = 10;
}
console.log(a); // 1
console.log(b); // b is not defined
可以看到上面代碼中,外層的console.log(b)是取不到的,因為b用let宣告,所以它只在{}中有效,超出範圍當然就取不到了
區塊範疇的好處不少,它能讓我們更靈活地管理變數,更簡潔明確的邏輯劃分,並把資訊的隱藏從函式層級進階到區塊層級,也更符合最小權限原則
舉個例子,這是一個看似平凡的for迴圈
for(var i=0; i<10; i++{
console.log(i)
}
但這裡的var i是全域變數,可是我們只會在for迴圈裡中使用這個i,那麼為何要污染全域範疇呢?
而且還會造成潛在的問題,如果i在其他地方被誤用? 如果某處又宣告了i造成命名衝突?
所以更好的做法是把它改成let,鎖在{}中。
Let與迴圈
如果我們在for迴圈使用let宣告,它除了會將變數鎖在for的{}之外,還會重新繫結到迴圈的每次迭代,確保每次的變數都是前一次迭代的結果。
來看一個面試中常見的題目,以下代碼的執行結果為?
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 0);
};
很多新手會答01234(我以前也是),但這是錯誤的,正解是 55555
原因是,var會讓i成為一個全域變數,而setTimeout會在for跑完之後才執行,所以當setTimeout實際執行時,這時i已經是5了
可能有人會問,為什麼setTimeout在for跑完之後才執行?
這牽扯到Event Queue的觀念,如果不了解,可以看我上一篇文章
理解Javascript中的Event Loop
要讓它正確顯示01234,很簡單,用let來宣告i就可以了,同時我們也知道它有重新繫結的特性,所以可以確保每次迭代中的i都會是前一次迭代的結果。
Reference:
You Don't Know JS: Scope & Closures