欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS中欺騙詞法作用域的eval和with詳解

 更新時間:2023年10月18日 15:37:09   作者:想做后端的前端  
詞法作用域就是定義在詞法階段的作用域,如果詞法作用域完全由寫代碼期間函數(shù)所聲明的位置來定義,怎樣才能在運行時來“修改”(也可以說欺騙)詞法作用域呢?JavaScript 中有兩種機制來實現(xiàn)這個目的,感興趣的朋友跟隨小編一起看看吧

1、詞法作用域

簡單地說,詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的,因此當詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)。

考慮以下代碼:

function foo(a) {
	var b = a * 2;
	function bar(c) {
		console.log( a, b, c );
	}
	bar( b * 3 );
}
foo( 2 ); // 2, 4, 12

在這個例子中有三個逐級嵌套的作用域。為了幫助理解,可以將它們想象成幾個逐級包含的氣泡。

在這里插入圖片描述

包含著整個全局作用域,其中只有一個標識符:foo。包含著 foo 所創(chuàng)建的作用域,其中有三個標識符:a、bar 和 b。包含著 bar 所創(chuàng)建的作用域,其中只有一個標識符:c。

作用域氣泡由其對應(yīng)的作用域塊代碼寫在哪里決定,它們是逐級包含的。

2、欺騙詞法

如果詞法作用域完全由寫代碼期間函數(shù)所聲明的位置來定義,怎樣才能在運行時來“修改”(也可以說欺騙)詞法作用域呢?
JavaScript 中有兩種機制來實現(xiàn)這個目的。社區(qū)普遍認為在代碼中使用這兩種機制并不是什么好注意。但是關(guān)于它們的爭論通常會忽略掉最重要的點:欺騙詞法作用域會導致性能下降。

2.1 eval

JavaScript 中的 eval(…) 函數(shù)可以接受一個字符串為參數(shù),并將其中的內(nèi)容視為好像在書寫時就存在于程序中這個位置的代碼。換句話說,可以在你寫的代碼中用程序生成代碼并運行,就好像代碼是寫在那個位置的一樣。

在執(zhí)行 eval(…) 之后的代碼時,引擎并不“知道”或“在意”前面的代碼是以動態(tài)形式插入進來,并對詞法作用域的環(huán)境進行修改的。引擎只會如往常地進行詞法作用域查找。

考慮以下代碼:

function foo(str, a) {
	eval( str ); // 欺騙!
	console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

eval(…) 調(diào)用中的 “var b = 3;” 這段代碼會被當作本來就在那里一樣來處理。由于那段代碼聲明了一個新的變量 b,因此它對已經(jīng)存在的 foo(…) 的詞法作用域進行了修改。事實上,和前面提到的原理一樣,這段代碼實際上在 foo(…) 內(nèi)部創(chuàng)建了一個變量 b,并遮蔽了外部(全局)作用域中的同名變量。

當 console.log(…) 被執(zhí)行時,會在 foo(…) 的內(nèi)部同時找到 a 和 b,但是永遠也無法找到外部的 b。因此會輸出“1, 3”而不是正常情況下會輸出的“1, 2”。

在嚴格模式的程序中,eval(…) 在運行時有其自己的詞法作用域,意味著其中的聲明無法修改所在的作用域。

function foo(str) {
	"use strict";
	eval( str );
	console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );

JavaScript 中 還 有 其 他 一 些 功 能 效 果 和 eval(…) 很 相 似。setTimeout(…) 和setInterval(…) 的第一個參數(shù)可以是字符串,字符串的內(nèi)容可以被解釋為一段動態(tài)生成的函數(shù)代碼。這些功能已經(jīng)過時且并不被提倡。不要使用它們!

new Function(…) 函數(shù)的行為也很類似,最后一個參數(shù)可以接受代碼字符串,并將其轉(zhuǎn)化為動態(tài)生成的函數(shù)(前面的參數(shù)是這個新生成的函數(shù)的形參)。這種構(gòu)建函數(shù)的語法比eval(…) 略微安全一些,但也要盡量避免使用。

在程序中動態(tài)生成代碼的使用場景非常罕見,因為它所帶來的好處無法抵消性能上的損失。

2.2 with

JavaScript 中另一個難以掌握(并且現(xiàn)在也不推薦使用)的用來欺騙詞法作用域的功能是with 關(guān)鍵字??梢杂泻芏喾椒▉斫忉?with,在這里我選擇從這個角度來解釋它:它如何同被它所影響的詞法作用域進行交互。
with 通常被當作重復(fù)引用同一個對象中的多個屬性的快捷方式,可以不需要重復(fù)引用對象本身
比如:

var obj = {
	a: 1,
	b: 2,
	c: 3
};
// 單調(diào)乏味的重復(fù) "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 簡單的快捷方式
with (obj) {
	a = 3;
	b = 4;
	c = 5;
}

但實際上這不僅僅是為了方便地訪問對象屬性??紤]如下代碼:

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ù)如果接受了含有一個或多個聲明的代碼,就會修改其所處的詞法作用域,而with 聲明實際上是根據(jù)你傳遞給它的對象憑空創(chuàng)建了一個全新的詞法作用域。

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

2.3 性能

eval(…) 和 with 會在運行時修改或創(chuàng)建新的作用域,以此來欺騙其他在書寫時定義的詞法作用域。
你可能會問,那又怎樣呢?如果它們能實現(xiàn)更復(fù)雜的功能,并且代碼更具有擴展性,難道不是非常好的功能嗎?答案是否定的。

JavaScript 引擎會在編譯階段進行數(shù)項的性能優(yōu)化。其中有些優(yōu)化依賴于能夠根據(jù)代碼的詞法進行靜態(tài)分析,并預(yù)先確定所有變量和函數(shù)的定義位置,才能在執(zhí)行過程中快速找到標識符。
但如果引擎在代碼中發(fā)現(xiàn)了 eval(…) 或 with,它只能簡單地假設(shè)關(guān)于標識符位置的判斷都是無效的,因為無法在詞法分析階段明確知道 eval(…) 會接收到什么代碼,這些代碼會如何對作用域進行修改,也無法知道傳遞給 with 用來創(chuàng)建新詞法作用域的對象的內(nèi)容到底是什么。

最悲觀的情況是如果出現(xiàn)了 eval(…) 或 with,所有的優(yōu)化可能都是無意義的,因此最簡單的做法就是完全不做任何優(yōu)化。

如果代碼中大量使用 eval(…) 或 with,那么運行起來一定會變得非常慢。無論引擎多聰明,試圖將這些悲觀情況的副作用限制在最小范圍內(nèi),也無法避免如果沒有這些優(yōu)化,代碼會運行得更慢這個事實。

到此這篇關(guān)于JS中欺騙詞法作用域的eval和with的文章就介紹到這了,更多相關(guān)js詞法作用域內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論