所謂作用域,就是一個聲明起作用的那一段的程序代碼。而作用域又分為詞法作用域(也叫靜態(tài)作用域)和動態(tài)作用域。靜態(tài)作用域是指在編譯的時候確定的,換句話說就是在聲明時就確定了所綁定的作用域。而動態(tài)作用域是指運(yùn)行時作用域,也就是說根據(jù)程序運(yùn)行時候的信息流來動態(tài)確定的。
詞法作用域(靜態(tài)作用域)
通常,大部分語言都是靜態(tài)作用域,Javascript也不例外,我們來看下面這幾段代碼
var foo='efg';
function a(){
console.log(foo) //會輸出什么呢?
}
function b(){
var foo = 'abc'
a()
}
b();//efg
function a(){
console.log(foo) //會輸出什么呢?
}
function b(){
var foo = 'abc'
a()
}
b();// foo is not defined.
為什么會出現(xiàn)上述的輸出呢,這就是作用域的問題,每個函數(shù)都有自己的執(zhí)行環(huán)境。靜態(tài)作用域代表a()的作用域是在a聲明的時候就確定了自己的作用域,也就是說a的作用域鏈,第一個對象是自己的arguments,第二個對象指向的是全局對象,所以在a()執(zhí)行時查找foo的時候并不會進(jìn)入到b()里面查找foo的值。
動態(tài)作用域
動態(tài)作用域不關(guān)心函數(shù)和作用域在哪聲明和如何聲明,只關(guān)心它們在何處調(diào)用。那么上述的代碼執(zhí)行結(jié)果將會是輸出'abc'。很好理解,在a()執(zhí)行時,需要訪問到foo的時候,是在a的執(zhí)行環(huán)境里面去查找foo,所以在b里面查找了foo的定義和聲明。
javascript中的作用域
javascript簡單明了,只有詞法作用域,但是如eval()、with、this機(jī)制,使用上去很像是動態(tài)作用域,因此使用上要特別注意。
with語句和try-catch語句中的catch塊會加長作用域鏈。在作用域前端添加一個變量對象。
作用域鏈
在B端,全局執(zhí)行環(huán)境被認(rèn)為是window對象,所有的全局變量以及函數(shù)都是作為window的屬性和方法而創(chuàng)建的。當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。作用域鏈的用途是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。作用域鏈最前端,總是指向當(dāng)前執(zhí)行代碼所在環(huán)境的變量對象。如果這個環(huán)境是一個函數(shù)作用域,則將其活動對象作為變量對象。作用域鏈的下一個變量對象是包含(外部)環(huán)境,直至到全局執(zhí)行環(huán)境,全局執(zhí)行環(huán)境的變量對象始終是作用域鏈中最后一個對象。搜索標(biāo)志符時總是逐級往上,直至找到標(biāo)志符為止。
window
--foo
--a()
--b()
--foo
Tips:ES6中引入了let方式聲明變量,也就是說引入了塊級作用域。在ES6以前,javascript不存在塊級作用域,所以在例如for if 中聲明的變量會被加入當(dāng)前的執(zhí)行環(huán)境。
Tips:關(guān)于ES6中箭頭函數(shù)(也就是lambda表達(dá)式)中的this其實(shí)是靜態(tài)作用域。