你不知道的javascript--eval,with

1.作用域
javascript如同其他語言一樣同樣的有其作用域,我們把javascript的作用域可看做是自定的一套規則,通過這套規則可以讓引擎來進行變量的查找。
作用域可以分為兩種。第一種是最為普遍的,被大多數的編程語言所采用的此法作用域。一種是動態作用域。讓我們來講講詞法作用域。

詞法作用域
大部分的編程語言在第一工資階段叫詞法化(也叫單詞化)。這概念是理解詞法作用域名稱來歷的基礎。

簡單的說就是,詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你寫在代碼時將變量和塊作用域寫在哪里決定的。因此當詞法分析器處理代碼時會保持作用域不變(大部分情況下)
查找:
作用域的結構和互相之間的位置關系給引擎提供了了位置信息進行查找。
在javascript中,引擎查找變量一般會從當前的作用域開始進行查找,若是在當前的作用域未找到變量就會向當前作用域所在的作用域查找,換句話說就是向父級作用域查找該變量。若是還未找到就繼續向上一級查找。要注意的是引擎會在查找中找到第一個匹配的為止。作用域查找始終會在運行所處的作用域作為最內部作用域開始查找。逐級向上進行查找。

2.騙人的eval和with
eval
javascript中的eval()可以接受一段字符串為參數,并且運行這段字符串。如同代碼就寫在此處一樣。
在執行eval()之后引擎并不知道前面的代碼時以動態的形式插入進來并進行欺騙。看下面一段代碼:
funtion test(str ,a) {
eval(str)
console.log(a, b)
}

var b = 2
test('var b = 3', 1)

你們猜猜會輸出什么?
答案是:1 3
為什么呢?
首先看一看test這函數
eval(str) => 運行了var b = 3
然后輸出了 a b
eval(..) 調用中的 "var b = 3;" 這段代碼會被當作本來就在那里一樣來處理。由于那段代 碼聲明了一個新的變量 b,因此它對已經存在的 foo(..) 的詞法作用域進行了修改。事實 上,和前面提到的原理一樣,這段代碼實際上在 foo(..) 內部創建了一個變量 b,并遮蔽 了外部(全局)作用域中的同名變量。當console.log()運行時候首先找到就是在test函數中的a,b。但是無法知道外部的b了。 值得注意的是在嚴格模式下eval有其自己的作用域。在javascript中有許多類似的例子 setTimeout(),setInterval();

with
JavaScript 中另一個難以掌握(并且現在也不推薦使用)的用來欺騙詞法作用域的功能是 with 關鍵字。
在js高級程序設計中是這樣描述with關鍵字的:with語句的作用是將代碼的作用域設置到一個特定的作用域中。
看段代碼:
但實際上這不僅僅是為了方便地訪問對象屬性。考慮如下代碼:
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 被泄漏到全局作用域上了!
這個例子中創建了 o1 和 o2 兩個對象。其中一個具有 a 屬性,另外一個沒有。foo(..) 函 數接受一個obj參數,該參數是一個對象引用,并對這個對象引用執行了with(obj) {..}。 在 with 塊內部,我們寫的代碼看起來只是對變量 a 進行簡單的詞法引用,實際上就是一個 LHS 引用(查看第 1 章),并將 2 賦值給它。
當我們將 o1 傳遞進去,a=2 賦值操作找到了 o1.a 并將 2 賦值給它,這在后面的 console. log(o1.a) 中可以體現。而當 o2 傳遞進去,o2 并沒有 a 屬性,因此不會創建這個屬性, o2.a 保持 undefined。
with可以將一個沒有或有多個屬性的對象處理為一個完全隔離的詞法作用域,因此這個對象的屬性也會被處理為定義在這個作用域的詞法標識符。

總的來說:
eval接受一段代碼,就會修改其所處的詞法作用域。而with聲明實際上是根據你傳遞給他的對象憑空創建一個全新的詞法作用域。

至于性能問題就是 盡量少用咯!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容