作用域即函數或變數的可見區域,白話點就是,函數或變數不在這個區域內,就無法獲取到。
用函數形式 function() {…}類似的代碼包起來的部分,即函數作用域。
與函數作用域相對應的概念是全局作用域,也就是定義在最外層的變數或函數,可以在任何地方訪問他們。
let a = 123 // 全域作用域
function func() {
var b = 456
console.log(a)
}
console.log(a) // apple
console.log(b) // Uncaught ReferenceError: b is not defined
func() //123
另一個例子 :
// 全域作用域
function func() { //作用域A
let a = "coffee"
function func1() { //作用域B
let a = "apple"
let b = "banana"
// 這裡可放許多要對外隱藏的變數
console.log(a);
}
console.log(a) // coffee
console.log(b) // Uncaught ReferenceError: b is not defined
func1() //apple
}
func();
等於有外層函數func的作用域A內嵌了函數func1的作用域B。在func裡面的console.log(a)訪問變數a時,JS引擎會先從離自己最近的作用域A查找變量a,找到就不再繼續查找,找不到就去上層作用域(此例中上層作用域是全域作用域)繼續查找,此例中a已經找到且值為"coffee",所以打印輸出coffee。依此類推,執行func1(),會執行func1函數內部的console.log(a),隨即會在作用域B查找裡面a,而作用域B裡面存在一個a的聲明和賦值語句let a = “apple” ,所以最先找到a的值是apple,找到便不再繼續查找,最終func1()輸出apple而不是coffee。
圖解上面程式碼的作用域:
在{}內用let關鍵字聲明的變數與函式(表達式)屬於塊級作用域(Object scope)。
但是這對ES6以前的代碼顯然產生很大的影響,出於兼容性的問題,在塊級聲明的函數依然可以在外部取用,如果需要函數只在塊級中取用,應該使用let關鍵字寫成函數表達式。以下例子:
function test() {
{
function inner() {
alert('inner function')
}
}
inner()
}
test() // inner function
上面例子證明JS引擎為了兼容在ES6實現中做了變通的處理,在看以下例子:
function run() {
var foo = "Foo";
let bar = "Bar";
console.log(foo, bar); // Foo Bar
{
var moo = "Mooo"
let baz = "Bazz";
console.log(moo, baz); // Mooo Bazz
}
console.log(moo); // Mooo
console.log(baz); // ReferenceError
}
run();
上面的例子用let聲明的函數,才是真正的塊級作用域。
之前文章也有提到過,常理是先聲明後使用,而var卻允許先使用後聲明:
console.log(a); // undefined
console.log(b); // ReferenceError
var a = "car"; // 聲明提前
let b = "123"; //由let聲明的不存在提前特性
for(var i = 0; i < 100; i++) {
// many code
}
// many code
console.log(i) // 100
循環裡面的i在循環結束後,並沒有回收掉,而是一直存在的垃圾變數,汙染了當前的環境。
for(let i = 0; i < 100; i++) {
// many code
}
// many code
console.log(i) // ReferenceError
所以應該使用let,避免使用var,除非目的是想要定義一個全域變數。
參考資料:
函式與作用域
Scope 作用域
Javascript 的作用域 (Scope) 與範圍鏈 (Scope Chain)