在javascript的學(xué)習(xí)中,執(zhí)行環(huán)境、作用域是2個(gè)非常非常重要和基本的概念,理解了這2個(gè)概念對(duì)于javsacript中很多腳本的運(yùn)行結(jié)果就能明白其中的道理了,比如搞清作用域和執(zhí)行環(huán)境對(duì)于閉包的理解至關(guān)重要。
一、執(zhí)行環(huán)境(exection context,也有稱之為執(zhí)行上下文)
執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象 (variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。雖然我們編寫的代碼無法訪問這個(gè)對(duì)象,但解析器會(huì)處理數(shù)據(jù)時(shí)會(huì)在后臺(tái)使用它。 ---《JavaScript高程》
當(dāng)這個(gè)執(zhí)行環(huán)境是函數(shù)的時(shí)候,這個(gè)變量對(duì)象就是函數(shù)的活動(dòng)對(duì)象?;顒?dòng)對(duì)象最開始時(shí)只包含一個(gè)變量,即 arguments 對(duì)象(這個(gè)對(duì)象在全局環(huán)境中是不存在的)。
二、作用域鏈(scope chain)
了解了變量對(duì)象之后,理解作用域鏈就容易了。
在代碼在一個(gè)環(huán)境執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè) 作用域鏈。作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。
整個(gè)作用域鏈?zhǔn)怯刹煌瑘?zhí)行位置上的變量對(duì)象(Variable Object)按照規(guī)則所構(gòu)建一個(gè)鏈表。
作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象。下一個(gè)變量對(duì)象來自包含(外部)環(huán)境,而下一個(gè)變量對(duì)象則來自下一個(gè)包含對(duì)象。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈的最后一個(gè)對(duì)象。
引用高程中的例子:
var color="blue";
function changecolor(){
var anothercolor="red";
function swapcolors(){
var tempcolor=anothercolor;
anothercolor=color;
color=tempcolor;
// Todo something
}
swapcolors();
}
changecolor();
//這里不能訪問tempcolor和anocolor;但是可以訪問color;
alert("Color is now "+color);
上面例子涉及了3個(gè)執(zhí)行環(huán)境:全局變量、changeColor()的局部變量環(huán)境和swapColors()的局部變量環(huán)境。swapcolor()的局部環(huán)境開始先在自己的Variable Object中搜索變量和函數(shù)名,找不到,則向上搜索changecolor作用域鏈。。。。。以此類推。但是,changecolor()函數(shù)是無法訪問swapcolor中的變量。

通過上面的分析,我們可以得知內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,但外部環(huán)境不能訪問內(nèi)部環(huán)境中的任何變量和函數(shù)。這些環(huán)境之間是線性、有次序的。每個(gè)環(huán)境都可以向上搜索作用域鏈,以便查詢變量和函數(shù)名;
tip:通過下面兩種情況,即當(dāng)執(zhí)行流進(jìn)入下列任何語句時(shí),作用域鏈就會(huì)得到加長:
- try-catch 語句的 catch 塊;
- with 語句。
三、沒有塊級(jí)作用域(在 let 出現(xiàn)以前)
看兩個(gè)例子
情況1:
for (var i = 0;i<10;i++) {
// do something
}
console.info(i); // 10
情況2:
for (let i = 0;i<10;i++) {
// do something
}
console.info(i); // error
從上面的例子中可以看出,JavaScript沒有像其他類C的語言(由花括號(hào)封閉的代碼塊都有自己的作用域)一樣。在使用for語句時(shí)尤其記住這點(diǎn)。
四、聲明變量
使用var聲明變量時(shí),這個(gè)變量將被自動(dòng)添加到距離最近的可用環(huán)境中。對(duì)于函數(shù)而言,自然聲明的變量就會(huì)被添加到函數(shù)的局部環(huán)境中,變量在整個(gè)函數(shù)環(huán)境內(nèi)都是可用的。
但是,如果變量沒有是用var進(jìn)行聲明,將會(huì)被添加到全局環(huán)境,也就是說成位全局變量了。(只有當(dāng)執(zhí)行流執(zhí)行到該語句時(shí),才會(huì)被添加到全局變量的變量對(duì)象中)
看一個(gè)例子:
情況1:
var x = 1;
function rain() {
console.info(x); // 1
}
rain();
情況2:
var x = 1;
function rain() {
console.info(x); //undefined
var x = 10;
console.info(x); // 10
}
rain();
情況1中得到的1 和 情況2中得到的10,通過上述作用域鏈的關(guān)系,我們較為容易理解。那情況2中的 undefined 是怎么回事兒呢?答案是預(yù)解析,我們看下面一段代碼,和上面的代碼等價(jià)
var x = 1;
function rain() {
var x;
console.info(x); //undefined
x = 10;
console.info(x); // 10
}
rain();
因?yàn)镴avaScript引擎會(huì)預(yù)解析代碼中變量和函數(shù)的定義。導(dǎo)致調(diào)用x的時(shí)候,局部執(zhí)行環(huán)境已經(jīng)有了x,就不向上繼續(xù)尋找x,也就訪問不到全局變量的x。而此時(shí)局部變量x還未賦值,這時(shí)候輸出也就是 undefined 了。