作用域
通俗來(lái)講瓷耙,作用域是一個(gè)變量或函數(shù)的作用范圍。作用域在函數(shù)定義時(shí)妻怎,就已經(jīng)確定了壳炎。目的是為了提高程序的可靠性,減少命名沖突逼侦。
作用域的分類
- 全局作用域
- 局部作用域(函數(shù)作用域)
根據(jù)作用域的不同匿辩,變量可以分為兩類: 全局變量和局部變量
全局變量
- 在全局作用域下聲明的變量,叫「全局變量」榛丢。在全局作用域的任何一地方铲球,都可以訪問(wèn)這個(gè)變量。
- 在全局作用域下晰赞,使用 var 聲明的變量是全局變量稼病。
- 在函數(shù)內(nèi)不使用 var 聲明的變量也是全局變量(不建議這么用)。
局部變量
- 定義在函數(shù)作用域的變量掖鱼,叫「局部變量」然走。
- 在函數(shù)內(nèi)部,使用 var 聲明的變量是局部變量戏挡。
- 函數(shù)的形參也是屬于局部變量芍瑞。
從執(zhí)行效率來(lái)看全局變量和局部變量: - 全局變量:只有瀏覽器關(guān)閉時(shí)才會(huì)被銷毀,比較占內(nèi)存褐墅。
- 局部變量:當(dāng)其所在的代碼塊運(yùn)行結(jié)束后拆檬,就會(huì)被銷毀,比較節(jié)約內(nèi)存空間妥凳。
作用域鏈
只有函數(shù)可以制造作用域結(jié)構(gòu)竟贯, 那么只要是代碼坝锰,就至少有一個(gè)作用域, 即全局作用域蔫饰。凡是代碼中有函數(shù)狈惫,那么這個(gè)函數(shù)就構(gòu)成另一個(gè)作用域捅位。如果函數(shù)中還有函數(shù)降传,那么在這個(gè)作用域中就又可以誕生一個(gè)作用域纠永。將這樣的所有的作用域列出來(lái)嚣鄙,可以有一個(gè)結(jié)構(gòu): 函數(shù)內(nèi)指向函數(shù)外的鏈?zhǔn)浇Y(jié)構(gòu)允蜈。就稱作作用域鏈磷箕。
- 當(dāng)在函數(shù)作用域操作一個(gè)變量時(shí)选酗,它會(huì)先在自身作用域中尋找,如果有就直接使用(就近原則)岳枷。如果沒(méi)有則向上一級(jí)作用域中尋找芒填,直到找到全局作用域;如果全局作用域中依然沒(méi)有找到空繁,則會(huì)報(bào)錯(cuò)殿衰。
- 在函數(shù)中要訪問(wèn)全局變量可以使用window對(duì)象。(比如說(shuō)盛泡,全局作用域和函數(shù)作用域都定義了變量a闷祥,如果想訪問(wèn)全局變量,可以使用window.a)
- 全局作用域在頁(yè)面打開(kāi)時(shí)創(chuàng)建傲诵,在頁(yè)面關(guān)閉時(shí)銷毀凯砍。
- 在全局作用域中有一個(gè)全局對(duì)象window,它代表的是一個(gè)瀏覽器的窗口拴竹,由瀏覽器創(chuàng)建悟衩,我們可以直接使用。全局作用域創(chuàng)建的變量都會(huì)作為window對(duì)象的屬性保存栓拜。比如在全局作用域內(nèi)寫(xiě) var a = 100座泳,這里的 a 等價(jià)于 window.a。創(chuàng)建的函數(shù)都會(huì)作為window對(duì)象的方法保存幕与。
預(yù)解析
JavaScript代碼的執(zhí)行是由瀏覽器中的JavaScript解析器來(lái)執(zhí)行的挑势。JavaScript解析器執(zhí)行JavaScript代碼的時(shí)候,分為兩個(gè)過(guò)程:預(yù)解析過(guò)程和代碼執(zhí)行過(guò)程
預(yù)解析過(guò)程
- 把變量的聲明提升到當(dāng)前作用域的最前面纽门,只會(huì)提升聲明薛耻,不會(huì)提升賦值。
- 把函數(shù)的聲明提升到當(dāng)前作用域的最前面赏陵,只會(huì)提升聲明饼齿,不會(huì)提升調(diào)用。
- 先提升var蝙搔,在提升function
變量的聲明提前
使用var關(guān)鍵字聲明的變量( 比如 var a = 1)缕溉,會(huì)在所有的代碼執(zhí)行之前被聲明(但是不會(huì)賦值),但是如果聲明變量時(shí)不是用var關(guān)鍵字(比如直接寫(xiě)a = 1)吃型,則變量不會(huì)被聲明提前证鸥。
所以,既然JS中存在變量提升的現(xiàn)象,那么枉层,在實(shí)戰(zhàn)開(kāi)發(fā)中泉褐,為了避免出錯(cuò),建議先聲明一個(gè)變量鸟蜡,然后再使用這個(gè)變量膜赃。
函數(shù)的聲明提前
使用函數(shù)聲明的形式創(chuàng)建的函數(shù)function foo(){},會(huì)被聲明提前揉忘。也就是說(shuō)跳座,整個(gè)函數(shù)會(huì)在所有的代碼執(zhí)行之前就被創(chuàng)建完成。所以泣矛,在代碼順序里疲眷,我們可以先調(diào)用函數(shù),再定義函數(shù)您朽。
fn1(); // 雖然 函數(shù) fn1 的定義是在后面狂丝,但是因?yàn)楸惶崆奥暶髁耍?所以此處可以調(diào)用函數(shù)
function fn1() {
console.log('上邊兒調(diào)用的時(shí)候,函數(shù)聲明已經(jīng)被提前了');
}
使用函數(shù)表達(dá)式創(chuàng)建的函數(shù)var foo = function(){}虚倒,不會(huì)被聲明提前美侦,所以不能在聲明前調(diào)用。
函數(shù)作用域
- 函數(shù)中魂奥,使用var關(guān)鍵字聲明的變量菠剩,會(huì)在函數(shù)中所有的代碼執(zhí)行之前被聲明。
- 函數(shù)中耻煤,沒(méi)有var聲明的變量都是全局變量具壮,而且并不會(huì)提前聲明。
注意哈蝇,定義形參就相當(dāng)于在函數(shù)作用域中聲明了變量棺妓。
function fun(e) { // 這個(gè)函數(shù)中,因?yàn)橛辛诵螀?e炮赦,此時(shí)就相當(dāng)于在函數(shù)內(nèi)部的第一行代碼里怜跑,寫(xiě)了 var e;
console.log(e);
}
函數(shù)預(yù)編譯步驟
預(yù)編譯四部曲:
- 函數(shù)在運(yùn)行的瞬間,生成一個(gè)執(zhí)行期上下文 (Active Object)吠勘,簡(jiǎn)稱AO性芬;
- 分析參數(shù)
2.1 函數(shù)接收形式參數(shù),添加到AO的屬性剧防,并且這個(gè)時(shí)候值為undefine,例如AO.age=undefined;
2.2 接收實(shí)參植锉,添加到AO的屬性,覆蓋之前的undefined; - 分析變量聲明峭拘,如var age;或var age=23;
3.1 如果上一步分析參數(shù)中AO還沒(méi)有age屬性俊庇,則添加AO屬性為undefined狮暑,即AO.age=undefined;
3.2 如果AO上面已經(jīng)有age屬性了,則不作任何修改; - 分析函數(shù)的聲明辉饱,如果有function age(){}搬男;把函數(shù)賦給AO.age ,覆蓋上一步分析的值;
執(zhí)行期上下文:
當(dāng)函數(shù)執(zhí)行時(shí),會(huì)創(chuàng)建一個(gè)稱為執(zhí)行期上下文的內(nèi)部對(duì)象鞋囊。一個(gè)執(zhí)行期上下文定義了一個(gè)函數(shù)執(zhí)行時(shí)的環(huán)境止后,函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的執(zhí)行上下文都是獨(dú)一無(wú)二的,所以多次調(diào)用一個(gè)函數(shù)會(huì)導(dǎo)致多個(gè)執(zhí)行上下文溜腐,當(dāng)函數(shù)執(zhí)行完畢,它所產(chǎn)生的執(zhí)行上下文被銷毀