今天是2016的第一天仲义,我們得揚帆起航踏上新的征程了婶熬。此篇闡述JavaScript中很重要的幾個概念:作用域與作用域鏈及相關(guān)知識點剑勾。
我們先從變量與作用域的行為關(guān)系開始討論。
變量作用域
JavaScript中赵颅,變量有全局變量及局部變量之分虽另,而能定義變量作用域的語塊只有函數(shù)。與局部變量有關(guān)的一種有趣特性饺谬,在此處不得不談--變量提升捂刺。
變量提升
變量提升為何物?
JavaScript的變量聲明會被提升到它們所在函數(shù)的頂部募寨,而初始化仍舊在原來的地方叠萍。JavaScript引擎并沒有重寫代碼:每次調(diào)用函數(shù)時,聲明都會重新提升绪商。
var name = 'Jog'; //全局變量
function prison() {
console.log(a); //輸出undefined
var a = 1;//局部變量
console.log(a); //輸出1
console.log(name); //輸出Jog
}
prison();
var name = 'Jog';
function prison() {
console.log(name); //輸出undefined
var name = 'Hans';
}
prison();
此處name的聲明被提升到函數(shù)的頂部辅鲸,變量查找時先從局部作用域開始格郁,未找到則由內(nèi)而外最后到全局作用域。
接下來我們詳細(xì)分析一下JavaScript的提升方式独悴。
變量提升與執(zhí)行環(huán)境對象
學(xué)習(xí)任何一門語言例书,都像學(xué)習(xí)魔術(shù)一樣,初時引人迷惑刻炒,驚嘆决采;然而當(dāng)秘密被揭開時幾乎令人失望,JavaScript不外如是坟奥。
- 提升
執(zhí)行某代碼塊時树瞭,JavaScript引擎先解釋,再運行爱谁。解釋過程主要幾個過程:
- (1) 聲明該作用域內(nèi)var變量
- (2) 聲明并初始化函數(shù)參數(shù)
- (3) 聲明并初始化聲明式函數(shù)
詳細(xì)可查看本系列筆記JavaScript之解釋與執(zhí)行機制
- 執(zhí)行環(huán)境與執(zhí)行環(huán)境對象
執(zhí)行環(huán)境(execution context)是一種概念晒喷,每當(dāng)函數(shù)被調(diào)用都會產(chǎn)生一個新的執(zhí)行環(huán)境。
執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù)访敌,決定了它們各自的行為凉敲。
- 1. 每個函數(shù)都有自己的執(zhí)行環(huán)境。 當(dāng)執(zhí)行流進入一個函數(shù)時寺旺,執(zhí)行環(huán)境就被推入一個環(huán)境棧中爷抓;函數(shù)執(zhí)行之后,棧將其執(zhí)行環(huán)境彈出阻塑,控制權(quán)返回到之前的執(zhí)行環(huán)境蓝撇。
- 2. 如果變量在當(dāng)前執(zhí)行環(huán)境內(nèi)可訪問,則該變量在當(dāng)前作用域內(nèi)叮姑。
- 3. JavaScript訪問變量唉地,其實就是訪問該執(zhí)行環(huán)境對象(變量對象)中的屬性据悔。
- 4. 全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境。
執(zhí)行環(huán)境對象--每個執(zhí)行環(huán)境都有一個與之對應(yīng)的變量對象耘沼,執(zhí)行環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中极颓。
function fn(arg) {
var name = 'Far';
inner();
function inner() {
console.log('inner');
}
}
fn('test');
在調(diào)用fn時,其過程如下
- 1. 創(chuàng)建一個空執(zhí)行環(huán)境對象群嗤;
- 2. 聲明參數(shù)并賦值菠隆;{arg: 1}
- 3. 聲明局部變量;{arg:1, name: undefined}
- 4. 預(yù)定義聲明式函數(shù)狂秘;{arg:1, name: undefined骇径, inner: function(){console.log('inner');}}
- 5. 代碼執(zhí)行時,局部變量被賦值者春;{...name: 'Far'...}
- 6. 執(zhí)行環(huán)境對象上變量和函數(shù)屬性保持不變破衔,調(diào)用inner函數(shù)時,其內(nèi)部會創(chuàng)建一個新的執(zhí)行環(huán)境對象钱烟,依此可遞歸形成一條作用域鏈晰筛。
作用域與作用域鏈
當(dāng)一個變量在某執(zhí)行回家內(nèi)可以被訪問,我們稱該變量在當(dāng)前作用域內(nèi)拴袭。
代碼某一執(zhí)行環(huán)境中執(zhí)行時读第,會創(chuàng)建該執(zhí)行環(huán)境對應(yīng)的變量對象的一個作用域鏈。
JavaScript引擎在執(zhí)行環(huán)境對象中查找作用域內(nèi)的變量或函數(shù)拥刻,其查找順序由內(nèi)而外向上直到全局執(zhí)行環(huán)境對象怜瞒,這個順序就形成作用域鏈。
作用域鏈的前端般哼,始終是當(dāng)前執(zhí)行環(huán)境對應(yīng)的變量對象吴汪。若此執(zhí)行環(huán)境是函數(shù),則將其活動對象作為變量對象逝她。作用域鏈中的下一個變量對象來自于當(dāng)前變量對象的包含(外部)執(zhí)行環(huán)境浇坐,如此一直到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對象始終是作用域鏈中的最后一個變量對象黔宛。
var age = 22;
var country = 'China';
var name = 'Java';
var job = 'Web';
function outer() {
console.log(age); //輸出22
console.log(country); //輸出undefined
var country = 'Union';
var name = 'Python';
inner();
function inner() {
console.log(name); //輸出Python
console.log(job); //輸出Web
}
}
outer();
代碼輸出結(jié)果如上:
- 1. outer函數(shù)執(zhí)行時近刘,首先在outer執(zhí)行環(huán)境對象中查找age和country變量結(jié)果country存在但并未初始化賦值,輸出undefined;而age未找到于是沿著作用域鏈向上到全局執(zhí)行環(huán)境臀晃,在其變量對象中存在age屬性觉渴,于是輸出其值22.
- 2. inner函數(shù)執(zhí)行時創(chuàng)建自己的執(zhí)行環(huán)境對象,其并沒有定義name和job等變量徽惋,于是沿著作用域鏈向上到達outer函數(shù)的執(zhí)行環(huán)境案淋,在其變量對象中存在name于是輸出其值Python;而未找到j(luò)ob于是繼續(xù)向上直到全局執(zhí)行環(huán)境险绘,找到并輸出其值踢京,結(jié)束誉碴;若依然未找到,則會報錯瓣距,停止運行黔帕。
注:函數(shù)參數(shù)亦被當(dāng)作變量對待,故其訪問規(guī)則與普通變量相同蹈丸。
延長作用域鏈
某些語句可以在作用域鏈的前端臨時增加一個變量對象成黄,該變量對象會在代碼執(zhí)行結(jié)束后移除。常見如:
- try-catch語句逻杖;
catch語句會創(chuàng)建一個新變量對象奋岁,包含被拋出的錯誤對象的聲明。 - with語句荸百;
with語句會創(chuàng)建一個包含語句接收對象的所有屬性和方法的變量對象闻伶。
function getAttr(data) {
var obj = data;
with(obj) {
var o = location;
}
console.log(o);
}
getAttr(window);
上面with語句接收window對象,其創(chuàng)建的變量對象就包含了window對象所有屬性和方法够话,于是可以在其執(zhí)行環(huán)節(jié)直接訪問location變量虾攻,也就是正常的window.location。
強烈建議不要使用with語句更鲁。
本篇筆記闡述JavaScript執(zhí)行環(huán)境與執(zhí)行環(huán)境對象,變量對象奇钞,作用域與作用域鏈澡为,耗時四小時,還有諸多不足之處待日后補充改進景埃。