一懒棉、作用域
所謂作用域,簡單來說就是變量和函數(shù)的可訪問范圍该抒。舉個(gè)例子:
function test() {
var a = 1;
console.log(a); // 輸出1
}
console.log(a); // 會(huì)報(bào)錯(cuò)慌洪,找不到a
上面test函數(shù)中的變量a擁有一個(gè)函數(shù)作用域,只能在函數(shù)里可以訪問到凑保,但在函數(shù)外面console.log(a)
則訪問不到會(huì)報(bào)錯(cuò)冈爹。
JS有兩個(gè)作用域,全局作用域和局部作用域(又叫函數(shù)作用域)欧引。
- 局部作用域:只在某個(gè)特定的代碼塊里才能訪問频伤。比如開頭的例子變量a,只能在函數(shù)包裹起來的那塊內(nèi)部區(qū)域里能被訪問维咸。常見于函數(shù)內(nèi)部,如下的形式中從 ‘{’ 開始到與之匹配的 ‘}’ 結(jié)束惠爽,for癌蓖、if、switch等包裹起來的不算婚肆。
function test() {
// 局部作用域
}
或者
var test = function() {
// 局部作用域
}
- 全局作用域:在代碼中所有地方都能夠訪問其中變量和函數(shù)的作用范圍租副。有下面三種情況可以擁有全局作用域:
- 最外層函數(shù)和最外層函數(shù)之外定義的變量擁有全局作用域。
- 未定義直接賦值的對象自動(dòng)變?yōu)槿肿兞拷闲裕瑩碛腥肿饔糜颉?/li>
- window對象的所有屬性擁有全局作用域用僧。
二、作用域鏈
在講作用域鏈前赞咙,我們先來講講變量對象责循。
- 變量對象:每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境,每個(gè)環(huán)境都有與之關(guān)聯(lián)的變量對象攀操,執(zhí)行環(huán)境中定義的變量和函數(shù)都保存在這個(gè)對象中院仿。由于在同個(gè)執(zhí)行環(huán)境里定義的變量和函數(shù)擁有相同的作用域,所以同個(gè)變量對象里保存的變量和函數(shù)擁有相同的作用域速和。(在web瀏覽器中歹垫,全局執(zhí)行環(huán)境對應(yīng)的變量對象為window對象,所以如上面所說window對象的屬性才會(huì)有全局作用域)颠放。
而代碼在環(huán)境中執(zhí)行時(shí)排惨,會(huì)創(chuàng)建變量對象的一個(gè)作用域鏈。作用域鏈的前端碰凶,是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對象暮芭,然后是包含環(huán)境(即當(dāng)前函數(shù)所在的執(zhí)行環(huán)境)的變量對象鹿驼,一直延續(xù)到全局執(zhí)行環(huán)境,即全局環(huán)境的變量對象始終是作用域鏈中的最后一個(gè)對象谴麦。舉個(gè)例子:
var outVariable = '最外層變量';
function outFunction() { // 最外層函數(shù)
var innerVariable = '內(nèi)部變量';
function innerFunction() { // 內(nèi)部函數(shù)
var variable = '內(nèi)部函數(shù)的變量';
}
}
上面這個(gè)例子蠢沿,在inner函數(shù)的執(zhí)行環(huán)境中的作用域鏈如下:
由圖可以看到,作用域鏈的開頭是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對象匾效,末尾是全局變量對象舷蟀。
那作用域鏈有什么用?作用域鏈的用途面哼,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問野宜。要理解這句話,重點(diǎn)在于理解有權(quán)和有序這兩個(gè)字魔策。
- 有權(quán):只有在當(dāng)前執(zhí)行環(huán)境的作用域鏈上變量對象的變量和函數(shù)才能訪問到匈子。
- 有序:當(dāng)查找一個(gè)變量時(shí),先在當(dāng)前的變量對象里找闯袒,找不到再到作用域鏈的上一個(gè)變量對象中找虎敦,一直到作用域鏈的末尾即全局變量對象,如果還沒有則報(bào)錯(cuò)政敢。
var outVariable = '最外層變量';
function outFunction() {
function firstFunc() {
var firstFuncVariable = '第一個(gè)函數(shù)的變量';
console.log(outVariable); // 輸出'最外層變量'
}
function secondFunc() {
var secondFuncVariable = '第二個(gè)函數(shù)的變量';
console.log(firstFuncVariable); // 報(bào)錯(cuò)
}
}
這段代碼中函數(shù)firstFunc和函數(shù)secondFunc的作用域鏈分別為:
根據(jù)上圖其徙,當(dāng)?shù)谝粋€(gè)函數(shù)要訪問outVariable變量時(shí),先找1里有沒有喷户,沒有再找2里也沒有唾那,再找3找到outVariable則返回。而第二個(gè)函數(shù)要訪問firstFuncVariable時(shí)褪尝,先找4沒有闹获,再找5沒有,最后找到6還沒有所以報(bào)錯(cuò)河哑,這個(gè)一層一層的搜索就是沿著作用域鏈的順序來的避诽。而secondFunc的作用域鏈上并沒有firstFunc的變量對象,所以無權(quán)訪問其中的變量璃谨。