JS學(xué)習(xí)系列 02 - 詞法作用域

1. 兩種作用域

“作用域”我們知道是一套規(guī)則凡资,用來(lái)管理引擎如何在當(dāng)前作用域以及嵌套的子作用域中根據(jù)標(biāo)識(shí)符名稱進(jìn)行變量查找松逊。

作用域有兩種主要工作模型:詞法作用域動(dòng)態(tài)作用域旧乞。

大多數(shù)語(yǔ)言采用的都是詞法作用域椭赋,少數(shù)語(yǔ)言采用動(dòng)態(tài)作用域(例如 Bash 腳本)辩恼,這里我們主要討論詞法作用域。

2. 詞法

大部分標(biāo)準(zhǔn)語(yǔ)言編譯器的第一個(gè)工作階段叫作詞法化瞧甩。
簡(jiǎn)單地說(shuō),詞法作用域是由你在寫代碼時(shí)將變量和函數(shù)(塊)作用域?qū)懺谀睦飦?lái)決定的弥鹦。當(dāng)然肚逸,也會(huì)有一些方法來(lái)動(dòng)態(tài)修改作用域,后邊我會(huì)介紹彬坏。

舉個(gè)例子:

var a = 2;

function foo1 () {
   console.log(a);
}

function foo2 () {
   var a = 10;

   foo1();
}

foo2();

這里輸出結(jié)果是多少呢朦促?

注意,這里結(jié)果打印的是 2栓始。

可能會(huì)有一些同學(xué)認(rèn)為是 10务冕,那就是沒有搞清楚詞法作用域的概念。
前邊介紹了幻赚,詞法作用域只取決于代碼書寫時(shí)的位置禀忆,那么在這個(gè)例子中臊旭,函數(shù) foo1 定義時(shí)的位置決定了它的作用域,通過(guò)下圖理解:

詞法作用域

foo1 和 foo2 都是分別定義在全局作用域中的函數(shù)箩退,它們是并列的离熏,所以在 foo1 的作用域鏈中并不包含 foo2 的作用域,雖然在 foo2 中調(diào)用了 foo1戴涝,但是 foo1 對(duì)變量 a 進(jìn)行 RHS 查詢時(shí)滋戳,在自己的作用域沒有找到,引擎會(huì)去 foo1 的上級(jí)作用域(也就是全局作用域)中查找啥刻,而并不會(huì)去 foo2 的作用域中查找奸鸯,最終在全局作用域中找到 a 的值為 2。

總結(jié)來(lái)說(shuō)可帽,無(wú)論函數(shù)在哪里被調(diào)用娄涩,也無(wú)論它如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時(shí)所處的位置決定蘑拯。

3. 欺騙詞法

JavaScript 中有 3 種方式可以用來(lái)“欺騙詞法”钝满,動(dòng)態(tài)改變作用域。

第一種: eval

JavaScript 中 eval(...) 函數(shù)可以接受一個(gè)字符串作為參數(shù)申窘,并將其中的內(nèi)容視為好像在書寫時(shí)就存在于程序中這個(gè)位置的代碼弯蚜。

在執(zhí)行 eval(...) 之后的代碼時(shí),引擎并不知道或在意前面的代碼是以動(dòng)態(tài)形式插入進(jìn)來(lái)并對(duì)詞法作用域環(huán)境進(jìn)行修改的剃法,引擎只會(huì)像往常一樣正常進(jìn)行詞法作用域的查找碎捺。

舉個(gè)例子:

function foo (str) {
   eval(str);        // "欺騙"詞法

   console.log(a);
}

var a = 2;

foo("var a = 10;");

如大家所想,輸出結(jié)果為 10贷洲。
因?yàn)?eval("var a = 10;") 在 foo 的作用域中新創(chuàng)建了一個(gè)同名變量 a收厨,引擎在 foo 作用域中對(duì) a 進(jìn)行 RHS 查詢,找到了新定義的 a优构,值為 10诵叁,所以不再向上查找全局作用域中的 a,所以導(dǎo)致輸出結(jié)果為 10钦椭,這就是 eval(...) 的作用拧额。

嚴(yán)格模式下,eval(...) 在運(yùn)行時(shí)有自己的詞法作用域彪腔,意味著其中的聲明無(wú)法修改所在的作用域侥锦。

'use strict;'

function foo (str) {
   eval(str);        // eval() 有自己的作用域,所以并不會(huì)修改 foo 的詞法作用域

   console.log(a);
}

var a = 2;

foo("var a = 10;");

這里輸出結(jié)果為 2德挣。

JavaScript 中還有一些功能和 eval(...) 類似的函數(shù)恭垦,例如 setTimeout(...) 和 setInterval(...) 的第一個(gè)參數(shù)可以是一個(gè)字符串,字符串的內(nèi)容可以解釋為一段動(dòng)態(tài)生成的代碼。這些功能已經(jīng)過(guò)時(shí)并且不被提倡番挺,最好不要使用它們唠帝。new Function(...) 函數(shù)的最后一個(gè)參數(shù)也可以接受代碼字符串,并將其轉(zhuǎn)化為動(dòng)態(tài)生成的函數(shù)建芙,也盡量避免使用没隘。

在程序中動(dòng)態(tài)生成代碼的使用場(chǎng)景非常罕見,因?yàn)樗鶐?lái)的好處無(wú)法抵消性能上的損失禁荸。

第二種: with
with 通常被當(dāng)做重復(fù)引用同一個(gè)對(duì)象中的多個(gè)屬性的快捷方式右蒲,可以不需要重復(fù)引用對(duì)象本身。

舉個(gè)例子:

var obj = {
   a: 2,
   b: 3
};

with (obj) {
   console.log(a);      // 2
   console.log(b);      // 3
   c = 4;         
};

console.log(c);          // 4, c 被泄露到全局作用域上

如上所示赶熟,我們對(duì) c 進(jìn)行 LHS 查詢瑰妄,因?yàn)樵?with 引入的新作用域中沒有找到 c,所以向上一級(jí)作用域(這里是全局作用域)查找映砖,也沒有找到间坐,在非嚴(yán)格模式下,在全局對(duì)象中新建了一個(gè)屬性 c 并賦值為 4邑退。

with 可以將一個(gè)沒有或有多個(gè)屬性的對(duì)象處理為一個(gè)完全隔離的詞法作用域竹宋,因此這個(gè)對(duì)象的屬性也會(huì)被處理為定義在這個(gè)作用域中的詞法標(biāo)識(shí)符。

盡管 with 塊可以將一個(gè)對(duì)象處理為詞法作用域地技,但是這個(gè)塊內(nèi)部正常的 var 聲明并不會(huì)限制在這個(gè)塊作用域中蜈七,而是被添加到 with 所處的函數(shù)作用域中。

嚴(yán)格模式下莫矗,with 被完全禁止使用飒硅。

'use strict';

var obj = {
   a: 2,
   b: 3
};

with (obj) {
   console.log(a);     
   console.log(b);      
   c = 4;         
};

console.log(c);       
嚴(yán)格模式下禁止使用with

第三種: try...catch
try...catch 可以測(cè)試代碼中的錯(cuò)誤。try 部分包含需要運(yùn)行的代碼作谚,而 catch 部分包含錯(cuò)誤發(fā)生時(shí)運(yùn)行的代碼三娩。

舉個(gè)例子:

try {
   foo();
} catch (err) {
   console.log(err);   

   var a = 2; 
// 打印出 "ReferenceError: foo is not defined at <anonymous>:2:4"
}

console.log(a);      // 2

當(dāng) try 中的代碼出現(xiàn)錯(cuò)誤時(shí),就會(huì)進(jìn)入 catch 塊妹懒,此時(shí)會(huì)把異常對(duì)象添加到作用域鏈的最前端雀监,類似于 with 一樣,catch 中定義的局部變量也都會(huì)添加到包含 try...catch 的函數(shù)作用域(或全局作用域)中眨唬。

4. 性能

JavaScript 引擎會(huì)在編譯階段進(jìn)行數(shù)項(xiàng)性能優(yōu)化滔悉。其中有些優(yōu)化依賴于能夠根據(jù)代碼的詞法進(jìn)行靜態(tài)分析,并預(yù)先確定所有變量和函數(shù)定義的位置单绑,才能在執(zhí)行過(guò)程中快速找到標(biāo)識(shí)符。

但如果引擎在代碼中發(fā)現(xiàn)了 eval(...)曹宴、with 和 try...catch 搂橙,它只能簡(jiǎn)單的假設(shè)關(guān)于標(biāo)識(shí)符位置的判斷都是無(wú)效的,因?yàn)闊o(wú)法在詞法分析階段明確知道 eval(...) 會(huì)接受到什么代碼,這些代碼會(huì)如何對(duì)作用域進(jìn)行修改区转,也無(wú)法知道傳遞給 with 用來(lái)創(chuàng)建新詞法作用域的對(duì)象的內(nèi)容到底是什么苔巨。

最悲觀的情況是如果出現(xiàn)了這些動(dòng)態(tài)添加作用域的代碼,所有的優(yōu)化可能都是無(wú)意義的废离,因此最簡(jiǎn)單的做法就是完全不進(jìn)行任何優(yōu)化侄泽。

如果代碼中大量使用 eval(...) 和 with,那么運(yùn)行起來(lái)一定會(huì)變得非常緩慢蜻韭。

5. 結(jié)論

很多時(shí)候我們對(duì)代碼的分析出錯(cuò)悼尾,就是源于對(duì)詞法作用域的忽略,所以讓我們重新審視代碼肖方,繼續(xù)努力闺魏!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市俯画,隨后出現(xiàn)的幾起案子析桥,更是在濱河造成了極大的恐慌,老刑警劉巖艰垂,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泡仗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡猜憎,警方通過(guò)查閱死者的電腦和手機(jī)娩怎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拉宗,“玉大人峦树,你說(shuō)我怎么就攤上這事〉┦拢” “怎么了魁巩?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)姐浮。 經(jīng)常有香客問(wèn)我谷遂,道長(zhǎng),這世上最難降的妖魔是什么卖鲤? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任肾扰,我火速辦了婚禮,結(jié)果婚禮上蛋逾,老公的妹妹穿的比我還像新娘集晚。我一直安慰自己,他們只是感情好区匣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布偷拔。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莲绰。 梳的紋絲不亂的頭發(fā)上欺旧,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音蛤签,去河邊找鬼辞友。 笑死,一個(gè)胖子當(dāng)著我的面吹牛震肮,可吹牛的內(nèi)容都是我干的称龙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钙蒙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茵瀑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起躬厌,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤马昨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扛施,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸿捧,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年疙渣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匙奴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妄荔,死狀恐怖泼菌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啦租,我是刑警寧澤哗伯,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站篷角,受9級(jí)特大地震影響焊刹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恳蹲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一虐块、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘉蕾,春花似錦贺奠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颁糟。三九已至,卻和暖如春喉悴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玖媚。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工箕肃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人今魔。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓勺像,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親错森。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吟宦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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