理解 Javascript 中變量的作用域

Javascript

Javascript 這門語言與其他的大部分語言相比,有很多特殊性,這是很多人喜歡它或者討厭它的原因员舵。其中變量的作用域問題,對很多初學(xué)者來說就是一個又一個「坑」藕畔。

變量的作用域在編程技能中算是一個基本概念,而在 Javascript 中庄拇,這一基本概念往往挑戰(zhàn)者初學(xué)者的常識注服。

基本的變量作用域

先上例子:

var scope = 'global';
function checkScope(){
    var scope = 'local';
    console.log(scope); // local
}
checkScope();
console.log(scope); // global

上面的例子中,聲明了全局變量 scope 和函數(shù)體內(nèi)的局部變量 scope措近。在函數(shù)體內(nèi)部溶弟,局部變量的優(yōu)先級比通明的全局變量要高,如果一個局部變量的名字與一個全局變量相同瞭郑,那么辜御,在聲明局部變量的函數(shù)體范圍內(nèi),局部變量將覆蓋同名的全局變量屈张。

下面再看一個例子:

scope = 'global';
function checkScope(){
    scope = 'local'; 
    console.log(scope); // local
    myScope = 'local';
    console.log(myScope); // local
}
checkScope();
console.log(scope); // local
console.log(myScope); // local

對于初學(xué)者來說擒权,可能會有兩個疑問:為什么在函數(shù)體外,scope 的值也變成了 local 阁谆?為什么在函數(shù)體外可以訪問 myScope 變量碳抄?

這兩個問題都源于一個特性。在全局作用域中聲明變量可以省略 var 關(guān)鍵字场绿,但是如果在函數(shù)體內(nèi)聲明變量時不使用 var 關(guān)鍵字剖效,就會發(fā)生上面的現(xiàn)象。首先焰盗,函數(shù)體內(nèi)的第一行語句璧尸,把全局變量中的 scope 變量的值改變了。而在聲明 myScope 變量時熬拒,由于沒有使用 var 關(guān)鍵字爷光,Javascript 就會在全局范圍內(nèi)聲明這個變量。因此梦湘,在聲明局部變量時使用 var 關(guān)鍵字是個很好的習(xí)慣瞎颗。

在 Javascript 中件甥,沒有「塊級作用域」一說

在 C 或者 Java 等語言中,if哼拔、for 等語句塊內(nèi)可以包含自己的局部變量引有,這些變量的作用域是這些語句的語句塊,而在 Javascript 中倦逐,不存在「塊級作用域」的說法譬正。

看下面的例子:

function checkScope(obj){
    var i = 0;
    if (typeof obj == 'object') {
        var j = 0;
        for (var k = 0; k < 10; k++) {
            console.log(k);
        }
        console.log(k);
    }
    console.log(j);
}
checkScope(new Object());

在上面的例子中,每一條控制臺輸出語句都能輸出正確的值檬姥,這是因為曾我,由于 Javascript 中不存在塊級作用域,因此健民,函數(shù)中聲明的所有變量抒巢,無論是在哪里聲明的,在整個函數(shù)中它們都是有定義的秉犹。

如果要更加強(qiáng)調(diào)上文中 函數(shù)中聲明的所有變量蛉谜,無論是在哪里聲明的,在整個函數(shù)中它們都是有定義的 這句話崇堵,那么還可以在后面跟一句話:函數(shù)中聲明的所有變量型诚,無論是在哪里聲明的,在整個函數(shù)中它們都是有定義的鸳劳,即使是在聲明之前狰贯。對于這句話,有個經(jīng)典的困擾初學(xué)者的「坑」赏廓。

var a = 2;
function test(){
    console.log(a);
    var a = 10;
}
test();

上面的例子中涵紊,控制臺輸出變量 a 的值為 undefined,既不是全局變量 a 的值 2,也不是局部變量 a 的值 10楚昭。首先栖袋,局部變量在整個函數(shù)體內(nèi)都是有定義的,因此抚太,局部變量 a 會在函數(shù)體內(nèi)覆蓋全局變量 a塘幅,而在函數(shù)體內(nèi),var 語句之前尿贫,它是不會被初始化的电媳。如果要讀取一個未被初始化的變量,將會得到一個默認(rèn)值 undefined庆亡。

所以匾乓,上面示例中的代碼與下面的代碼時等價的:

var a = 2;
function test(){
    var a;
    console.log(a);
    a = 10;
}
test();

可見,把所有的函數(shù)聲明集合起來放在函數(shù)的開頭是個良好的習(xí)慣又谋。

變量的真相

可能很多人已經(jīng)注意到拼缝,在 Javascript 當(dāng)中娱局,一個變量與一個對象的一個屬性,有很多相似的地方咧七,實際上衰齐,它們并沒有什么本質(zhì)區(qū)別。在 Javascript 中继阻,任何變量都是某個特定對象的屬性耻涛。

全局變量都是全局對象的屬性。在 Javascript 解釋器開始運(yùn)行且沒有執(zhí)行 Javascript 代碼之前瘟檩,會有一個「全局對象」被創(chuàng)建抹缕,然后 Javascript 解釋器會給它與定義一些屬性,這些屬性就是我們在 Javascript 代碼中可以直接使用的內(nèi)置的變量和方法墨辛。之后卓研,每當(dāng)我們定義一個全局變量,實際上是給全局對象定義了一個屬性睹簇。

在客戶端的 Javascript 當(dāng)中鉴分,這個全局變量就是 Window 對象,它有一個指向自己的屬性 window 带膀,這就是我們常用的全局變量。

對于函數(shù)的局部變量橙垢,則是在函數(shù)開始執(zhí)行時垛叨,會有一個對應(yīng)的「調(diào)用對象」被創(chuàng)建,函數(shù)的局部變量都作為它的屬性而存儲柜某。這樣可以防止局部變量覆蓋全局變量嗽元。

作用域鏈

如果要深入理解 Javascript 中變量的作用域,那就必須拿出「作用域鏈」這個終極武器喂击。

首先要理解的一個名詞就是「執(zhí)行環(huán)境」剂癌,每當(dāng) Javascript 執(zhí)行時,都會有一個對應(yīng)的執(zhí)行環(huán)境被創(chuàng)建翰绊,這個執(zhí)行環(huán)境中很重要的一部分就是函數(shù)的調(diào)用對象(前面說過佩谷,調(diào)用對象是用來存儲相應(yīng)函數(shù)的局部變量的對象),每一個 Javascript 方法都是在自己獨(dú)有的執(zhí)行環(huán)境中運(yùn)行的监嗜。簡而言之谐檀,函數(shù)的執(zhí)行環(huán)境包含了調(diào)用對象,調(diào)用對象的屬性就是函數(shù)的局部變量裁奇,每個函數(shù)就是在這樣的執(zhí)行環(huán)境中執(zhí)行桐猬,而在函數(shù)之外的代碼,也在一個執(zhí)行環(huán)境中刽肠,這個執(zhí)行環(huán)境包含了全局變量溃肪。

在 Javascript 的執(zhí)行環(huán)境中免胃,還有一個與之對應(yīng)的「作用域鏈」,它是一個由對象組成的列表或鏈惫撰。

當(dāng) Javascript 代碼需要查詢一個變量 x 的時候羔沙,會有一個被稱為「變量名解析」的過程。它會首先檢查作用域鏈的第一個對象润绎,如果這個對象包含名為 x 的屬性撬碟,那么就采用這個屬性的值,否則莉撇,會繼續(xù)檢查第二個對象呢蛤,依此類推。當(dāng)檢查到最后一個對象的時候仍然沒有相應(yīng)的屬性棍郎,則這個變量會被認(rèn)定為是「未定義」的其障。

在全局的 Javascript 執(zhí)行環(huán)境中,作用域鏈中只包含一個對象涂佃,就是全局對象励翼。而在函數(shù)的執(zhí)行環(huán)境中,則同時包含函數(shù)的調(diào)用對象辜荠。由于 Javascript 的函數(shù)是可以嵌套的汽抚,因此每個函數(shù)執(zhí)行環(huán)境的作用域鏈可能包含不同數(shù)目個對象,一個非嵌套的函數(shù)的執(zhí)行環(huán)境中伯病,作用域鏈包含了這個函數(shù)的調(diào)用對象和全局對象造烁,而在嵌套的函數(shù)的執(zhí)行環(huán)境中,作用域鏈包含了嵌套的每一層函數(shù)的調(diào)用對象以及全局變量午笛。

我們可以用一個圖來直觀地解釋作用域鏈和變量名解析的過程:

作用域鏈和變量名解析的過程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惭蟋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子药磺,更是在濱河造成了極大的恐慌告组,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癌佩,死亡現(xiàn)場離奇詭異木缝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)围辙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門氨肌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酌畜,你說我怎么就攤上這事怎囚。” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵恳守,是天一觀的道長考婴。 經(jīng)常有香客問我,道長催烘,這世上最難降的妖魔是什么沥阱? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮伊群,結(jié)果婚禮上考杉,老公的妹妹穿的比我還像新娘。我一直安慰自己舰始,他們只是感情好崇棠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丸卷,像睡著了一般枕稀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谜嫉,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天萎坷,我揣著相機(jī)與錄音,去河邊找鬼沐兰。 笑死哆档,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的住闯。 我是一名探鬼主播虐呻,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寞秃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起偶惠,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤春寿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忽孽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绑改,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年兄一,在試婚紗的時候發(fā)現(xiàn)自己被綠了厘线。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡出革,死狀恐怖造壮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤耳璧,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布成箫,位于F島的核電站,受9級特大地震影響旨枯,放射性物質(zhì)發(fā)生泄漏蹬昌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一攀隔、第九天 我趴在偏房一處隱蔽的房頂上張望皂贩。 院中可真熱鬧,春花似錦昆汹、人聲如沸明刷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遮精。三九已至,卻和暖如春败潦,著一層夾襖步出監(jiān)牢的瞬間本冲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工劫扒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留檬洞,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓沟饥,卻偏偏與公主長得像添怔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贤旷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容