JavaScript中eval和with語(yǔ)句如何影響作用域鏈的深度探索
前言
在上篇文章中,我們介紹了深度剖析了作用域,并將其定義為一套規(guī)則,這套規(guī)則用來(lái)管理引擎如何在當(dāng)前作用域以及嵌套的子作用域中根據(jù)標(biāo)識(shí)符名稱進(jìn)行變量查找。
上篇文章入口:JavaScript作用域深度剖析:從局部到全局一網(wǎng)打盡
而作用域一共分為兩種:詞法作用域 和 動(dòng)態(tài)作用域, 而本篇文章我們將深入 詞法作用域,讓我們一起來(lái)了解一下吧。
詞法階段
簡(jiǎn)單來(lái)說(shuō),詞法作用域就是定義在詞法階段的作用域。換句話說(shuō),詞法作用域是由你在寫代碼時(shí)將變量和塊作用域?qū)懺谀睦餂Q定的。

上述代碼作用域:
包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符: foo
包含著 foo 所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符: a、bar、b
包含著 bar 所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符:c
- 在此,只要假設(shè)每個(gè)函數(shù)都會(huì)創(chuàng)建一個(gè)新的作用域氣泡即可。
- 記住:作用域查找會(huì)在找到第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止。在多層的嵌套作用域中可定義同名的標(biāo)識(shí)符,這叫做"遮蔽效應(yīng)"(內(nèi)部標(biāo)識(shí)符 "遮蔽" 了外部的標(biāo)識(shí)符)。
- 作用域查找規(guī)則:作用域查找始終從運(yùn)行時(shí)所處的最內(nèi)部作用域開始,逐級(jí)向外或者說(shuō)向上進(jìn)行,直到遇見第一個(gè)匹配的標(biāo)識(shí)符為止。
無(wú)論函數(shù)在哪里被調(diào)用,或如何被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時(shí)所處的位置決定。
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3);
}
foo(2); // 2 4 12- 詞法作用域只會(huì)查找一級(jí)標(biāo)識(shí)符,比如a、b、c。如果代碼中引用了 foo.bar.baz, 詞法作用域查找只會(huì)試圖查找 foo 標(biāo)識(shí)符,找到這個(gè)變量后,對(duì)象屬性訪問(wèn)規(guī)則會(huì)分別接管與 bar 和 baz 屬性的訪問(wèn)。
欺騙詞法
JavaScript 有兩種機(jī)制來(lái)實(shí)現(xiàn)這個(gè)目的。
eval(不推薦使用)
- JS 中的 eval(...) 函數(shù)可接收一個(gè)字符串作為參數(shù)。換句話說(shuō),在此位置寫的內(nèi)容就好像是寫在那個(gè)位置上的代碼一樣。根據(jù)這個(gè)原理來(lái)理解 eval(...) 它是如何通過(guò)代碼欺騙和假裝成書寫時(shí)代碼就在那,來(lái)實(shí)現(xiàn)修改詞法作用域環(huán)境的。
在執(zhí)行 eval(...) 之后的代碼時(shí),引擎并不 知道 或 在意 前面的代碼是否以動(dòng)態(tài)形式插入進(jìn)來(lái)的,并對(duì)詞法作用域的環(huán)境進(jìn)行修改的。引擎只會(huì)如往常地進(jìn)行此法作用域查找。
function foo(str, a) {
eval(str); // 欺騙!
console.log(a, b); // 1 3
}
var b = 2;
foo("var b = 3;", 1);- eval(...) 調(diào)用中的 var b = 3; 這段代碼就會(huì)被當(dāng)做本來(lái)就在那里一樣來(lái)處理。由于這段代碼聲明了一個(gè)新的變量 b,因此它對(duì)已經(jīng)存在的 foo(...) 詞法作用域進(jìn)行了修改。事實(shí)上,這段代碼再 foo(...) 內(nèi)部創(chuàng)建了一個(gè)變量 b,并遮蔽了外部(全局)作用域中的同名變量。
- 當(dāng)執(zhí)行 console.log(...) 時(shí),會(huì)在 foo(...) 的內(nèi)部找到 a 和 b,但永遠(yuǎn)無(wú)法找到外部的 b。因此會(huì)輸出 1, 3, 而不是正常情況下輸出的 1, 2。
- 默認(rèn)情況下,eval(...)中所執(zhí)行的代碼中包含一個(gè)或多個(gè)聲明(無(wú)論是變量還是函數(shù)),都會(huì)對(duì) eval(...) 所處的詞法作用域進(jìn)行修改。
嚴(yán)格模式下:eval(...)在運(yùn)行時(shí)有著自己的詞法作用域,意味著其中的聲明無(wú)法修改所在的作用域。
function foo(str) {
'use strict';
eval(str);
console.log(a); // ReferenceError: a is not defined
}
foo("var b = 3;");with(不推薦使用)
with 通常被當(dāng)做重復(fù)引用同一個(gè)對(duì)象中多個(gè)屬性的快捷方式。
var obj = {
a: 1,
b: 2,
c: 3
};
// 單調(diào)乏味的重復(fù)"obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡(jiǎn)單的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}其實(shí)不僅僅是為了方便訪問(wèn)對(duì)象屬性,例如:
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!- 這個(gè)例子中創(chuàng)建了 o1、o2 兩個(gè)對(duì)象,其中一個(gè)具有 a 屬性,另一個(gè)沒(méi)有。foo(...) 函數(shù)接收一個(gè) obj 參數(shù),該參數(shù)是一個(gè)對(duì)象引用,并對(duì)這個(gè)對(duì)象引用執(zhí)行了 with(obj){...}。在 with 內(nèi)部,只是對(duì)變量 a 進(jìn)行了簡(jiǎn)單的詞法引用,實(shí)際上就是一個(gè) LHS, 并將 2 復(fù)制給了它。
- 當(dāng)我們將 o1 傳遞進(jìn)去,a = 2 賦值操作找到了 o1.a 并將 2 賦值給它,這在后面的console.log(o1.a) 中可以體現(xiàn)出來(lái)。而當(dāng) o2 傳遞進(jìn)去,o2 沒(méi)有 a 屬性,因此不會(huì)創(chuàng)建一個(gè)屬性,o2.a 保持 undefined。
但是可以注意到一個(gè)奇怪的副作用,實(shí)際上 a = 2 賦值操作創(chuàng)建了一個(gè)全局的變量 a。這是怎么回事?
- with 可將一個(gè)沒(méi)有或有多個(gè)屬性的對(duì)象處理為一個(gè)完全隔離的詞法作用域,因此這個(gè)對(duì)象的屬性會(huì)被處理為定義在這個(gè)作用域中的詞法標(biāo)識(shí)符。
- 盡管 with 塊可將一個(gè)對(duì)象處理為詞法作用域,但這個(gè)塊內(nèi)中正常的 var 聲明并不會(huì)被限制在這個(gè)塊的作用域中,而是被添加到 with 所處的函數(shù)作用域中。
eval 與 with 的區(qū)別?
- eval(...) 函數(shù)接收一個(gè)或多個(gè)聲明的代碼,會(huì)修改其所處的詞法作用域,而 with 聲明實(shí)際上是根據(jù)你傳遞給它的對(duì)象憑空創(chuàng)建一個(gè)全新的詞法作用域。
- 另外不推薦使用 eval(...) 和 with(...){...} 的原因是會(huì)被嚴(yán)格模式所影響(限制)。with 被完全禁止,而在保留核心功能的前提下,間接或非安全地使用 eval(...) 也被禁止了。
性能
- 你可能會(huì)問(wèn),如果他們能實(shí)現(xiàn)更復(fù)雜的功能,并且代碼更具有擴(kuò)展性,難道不是非常好的功能嗎?答案是否定的。
- 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,它只能簡(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)了 eval(..) 或 with,所有的優(yōu)化可能都是無(wú)意義的,因此最簡(jiǎn)單的做法就是完全不做任何優(yōu)化。
- 如果代碼中大量使用 eval(..) 或 with,那么運(yùn)行起來(lái)一定會(huì)變得非常慢。無(wú)論引擎多聰明,試圖將這些悲觀情況的副作用限制在最小范圍內(nèi),也無(wú)法避免如果沒(méi)有這些優(yōu)化,代碼會(huì)運(yùn)行得更慢這個(gè)事實(shí)。
小結(jié)
- 詞法作用域意味著作用域是由書寫代碼時(shí)函數(shù)聲明的位置來(lái)決定。
JavaScript 有兩種機(jī)制可欺騙詞法作用域:eval(...) 和 with(...){...}。
- eval(...): 修改所處位置的詞法作用域。
- with(...){...}: 將對(duì)象的引用當(dāng)做作用域來(lái)處理,將對(duì)象中的屬性當(dāng)做作用域中標(biāo)識(shí)符來(lái)處理,從而創(chuàng)建一個(gè)新的詞法作用域。
- eval(...) 和 with(...){...} 這兩個(gè)機(jī)制的副作用是引擎無(wú)法在編譯時(shí)對(duì)作用域查找進(jìn)行優(yōu)化的。所以, 不要使用他們。
特殊字符描述:
- 問(wèn)題標(biāo)注 Q:(question)
- 答案標(biāo)注 R:(result)
- 注意事項(xiàng)標(biāo)準(zhǔn):A:(attention matters)
- 詳情描述標(biāo)注:D:(detail info)
- 總結(jié)標(biāo)注:S:(summary)
- 分析標(biāo)注:Ana:(analysis)
- 提示標(biāo)注:T:(tips)
以上就是JavaScript中eval和with語(yǔ)句如何影響作用域鏈的深度探索的詳細(xì)內(nèi)容,更多關(guān)于JS eval with作用域鏈的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實(shí)現(xiàn)圖片本地預(yù)覽功能【不用上傳至服務(wù)器】
這篇文章主要介紹了JavaScript實(shí)現(xiàn)圖片本地預(yù)覽功能,針對(duì)非IE瀏覽器的HTML5濾鏡功能及IE瀏覽器的相關(guān)組件功能實(shí)現(xiàn)不上傳至服務(wù)器預(yù)覽本地圖片的效果,需要的朋友可以參考下2017-09-09
javascript+html5+css3自定義彈出窗口效果
這篇文章主要為大家詳細(xì)介紹了javascript+html5+css3自定義彈出窗口效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Javascript打印網(wǎng)頁(yè)部分內(nèi)容的腳本
有時(shí)候我們只需要打印部分內(nèi)容,因?yàn)楝F(xiàn)在的頁(yè)面中廣告和一些相關(guān)內(nèi)容很多,所有用下面的方法,就可以了2008-11-11
js實(shí)現(xiàn)獲取兩個(gè)日期之間所有日期的方法
這篇文章主要介紹了js實(shí)現(xiàn)獲取兩個(gè)日期之間所有日期的方法,涉及javascript針對(duì)日期與時(shí)間的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
js實(shí)現(xiàn)年月日表單三級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)年月日表單三級(jí)聯(lián)動(dòng),生日欄表單三級(jí)聯(lián)動(dòng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03

