JS中欺騙詞法作用域的eval和with詳解
1、詞法作用域
簡(jiǎn)單地說(shuō),詞法作用域就是定義在詞法階段的作用域。換句話說(shuō),詞法作用域是由你在寫(xiě)代碼時(shí)將變量和塊作用域?qū)懺谀睦飦?lái)決定的,因此當(dāng)詞法分析器處理代碼時(shí)會(huì)保持作用域不變(大部分情況下是這樣的)。
考慮以下代碼:
function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar( b * 3 );
}
foo( 2 ); // 2, 4, 12在這個(gè)例子中有三個(gè)逐級(jí)嵌套的作用域。為了幫助理解,可以將它們想象成幾個(gè)逐級(jí)包含的氣泡。

包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符:foo。包含著 foo 所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符:a、bar 和 b。包含著 bar 所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符:c。
作用域氣泡由其對(duì)應(yīng)的作用域塊代碼寫(xiě)在哪里決定,它們是逐級(jí)包含的。
2、欺騙詞法
如果詞法作用域完全由寫(xiě)代碼期間函數(shù)所聲明的位置來(lái)定義,怎樣才能在運(yùn)行時(shí)來(lái)“修改”(也可以說(shuō)欺騙)詞法作用域呢?
JavaScript 中有兩種機(jī)制來(lái)實(shí)現(xiàn)這個(gè)目的。社區(qū)普遍認(rèn)為在代碼中使用這兩種機(jī)制并不是什么好注意。但是關(guān)于它們的爭(zhēng)論通常會(huì)忽略掉最重要的點(diǎn):欺騙詞法作用域會(huì)導(dǎo)致性能下降。
2.1 eval
JavaScript 中的 eval(…) 函數(shù)可以接受一個(gè)字符串為參數(shù),并將其中的內(nèi)容視為好像在書(shū)寫(xiě)時(shí)就存在于程序中這個(gè)位置的代碼。換句話說(shuō),可以在你寫(xiě)的代碼中用程序生成代碼并運(yùn)行,就好像代碼是寫(xiě)在那個(gè)位置的一樣。
在執(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 );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3eval(…) 調(diào)用中的 “var b = 3;” 這段代碼會(huì)被當(dāng)作本來(lái)就在那里一樣來(lái)處理。由于那段代碼聲明了一個(gè)新的變量 b,因此它對(duì)已經(jīng)存在的 foo(…) 的詞法作用域進(jìn)行了修改。事實(shí)上,和前面提到的原理一樣,這段代碼實(shí)際上在 foo(…) 內(nèi)部創(chuàng)建了一個(gè)變量 b,并遮蔽了外部(全局)作用域中的同名變量。
當(dāng) console.log(…) 被執(zhí)行時(shí),會(huì)在 foo(…) 的內(nèi)部同時(shí)找到 a 和 b,但是永遠(yuǎn)也無(wú)法找到外部的 b。因此會(huì)輸出“1, 3”而不是正常情況下會(huì)輸出的“1, 2”。
在嚴(yán)格模式的程序中,eval(…) 在運(yùn)行時(shí)有其自己的詞法作用域,意味著其中的聲明無(wú)法修改所在的作用域。
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );JavaScript 中 還 有 其 他 一 些 功 能 效 果 和 eval(…) 很 相 似。setTimeout(…) 和setInterval(…) 的第一個(gè)參數(shù)可以是字符串,字符串的內(nèi)容可以被解釋為一段動(dòng)態(tài)生成的函數(shù)代碼。這些功能已經(jīng)過(guò)時(shí)且并不被提倡。不要使用它們!
new Function(…) 函數(shù)的行為也很類(lèi)似,最后一個(gè)參數(shù)可以接受代碼字符串,并將其轉(zhuǎn)化為動(dòng)態(tài)生成的函數(shù)(前面的參數(shù)是這個(gè)新生成的函數(shù)的形參)。這種構(gòu)建函數(shù)的語(yǔ)法比eval(…) 略微安全一些,但也要盡量避免使用。
在程序中動(dòng)態(tài)生成代碼的使用場(chǎng)景非常罕見(jiàn),因?yàn)樗鶐?lái)的好處無(wú)法抵消性能上的損失。
2.2 with
JavaScript 中另一個(gè)難以掌握(并且現(xiàn)在也不推薦使用)的用來(lái)欺騙詞法作用域的功能是with 關(guān)鍵字??梢杂泻芏喾椒▉?lái)解釋 with,在這里我選擇從這個(gè)角度來(lái)解釋它:它如何同被它所影響的詞法作用域進(jìn)行交互。
with 通常被當(dāng)作重復(fù)引用同一個(gè)對(duì)象中的多個(gè)屬性的快捷方式,可以不需要重復(fù)引用對(duì)象本身
比如:
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 被泄漏到全局作用域上了!eval(…) 函數(shù)如果接受了含有一個(gè)或多個(gè)聲明的代碼,就會(huì)修改其所處的詞法作用域,而with 聲明實(shí)際上是根據(jù)你傳遞給它的對(duì)象憑空創(chuàng)建了一個(gè)全新的詞法作用域。
盡管 with 塊可以將一個(gè)對(duì)象處理為詞法作用域,但是這個(gè)塊內(nèi)部正常的 var聲明并不會(huì)被限制在這個(gè)塊的作用域中,而是被添加到 with 所處的函數(shù)作用域中。
2.3 性能
eval(…) 和 with 會(huì)在運(yùn)行時(shí)修改或創(chuàng)建新的作用域,以此來(lái)欺騙其他在書(shū)寫(xiě)時(shí)定義的詞法作用域。
你可能會(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í)。
到此這篇關(guān)于JS中欺騙詞法作用域的eval和with的文章就介紹到這了,更多相關(guān)js詞法作用域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js正則表達(dá)exec與match的區(qū)別說(shuō)明
本篇文章主要是對(duì)js正則表達(dá)exec與match的區(qū)別進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
js jquery ajax的幾種用法總結(jié)(及優(yōu)缺點(diǎn)介紹)
本篇文章只要介紹了js jquery ajax的幾種用法總結(jié)(及優(yōu)缺點(diǎn)介紹),需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
微信小程序提取公用函數(shù)到util.js及使用方法示例
這篇文章主要介紹了微信小程序提取公用函數(shù)到util.js及使用方法,結(jié)合實(shí)例形式分析了util.js公用函數(shù)相關(guān)調(diào)用操作技巧,需要的朋友可以參考下2019-01-01
javascript 獲取鼠標(biāo)的絕對(duì)位置 event
javascript 獲取鼠標(biāo)的絕對(duì)位置 event2009-06-06
一個(gè)JS函數(shù)搞定網(wǎng)頁(yè)標(biāo)題(title)閃動(dòng)效果
這篇文章主要介紹了使用JS函數(shù)實(shí)現(xiàn)網(wǎng)頁(yè)標(biāo)題(title)閃動(dòng)效果的代碼,需要的朋友可以參考下2014-05-05
三種動(dòng)態(tài)加載js的jquery實(shí)例代碼另附去除js方法
這篇文章主要介紹了三種動(dòng)態(tài)加載js的jquery實(shí)例代碼另附去除js方法,需要的朋友可以參考下2014-04-04
JavaScript中SetInterval與setTimeout的用法詳解
在寫(xiě)H5游戲時(shí)經(jīng)常需要使用定時(shí)刷新頁(yè)面實(shí)現(xiàn)動(dòng)畫(huà)效果,比較常用即setTimeout()以及setInterval(),但是大家對(duì)SetInterval與setTimeout的用法了解嗎,下面通過(guò)本文給大家詳解js中SetInterval與setTimeout的用法,需要的朋友參考下2015-11-11

