正常來說,執行期上下文的作用域鍊是不會改變的
JavaScript中的詞法作用域並不是一成不變的
(詞法作用域/靜態作用域:作用域由書寫程式碼時函數宣告位置決定)
有幾種機制是可以欺騙詞法的
它們是with()、eval()還有try-catch語句的catch子句
其中with和eval我們不應該去使用(會產生很多問題)
欺騙詞法的意思就是欺騙詞法作用域
也就是說,它們在運行時改變了作用域鏈
下面我就來談談這些可以欺騙詞法的機制
eval()函數接受一個字串作為參數,並且解析字串產生程式碼
var a = 123;eval('console.log(a)');// 123
於是控制台列印了123
執行了eval函數之後
js引擎並不知道這段程式碼時動態插入的,並且修改了作用域鏈
引擎還會像往常一樣查找作用域鏈
看下面的程式碼
var a = 1;function demo(){ eval('var a = 2;');//欺骗词法 console.log(a);// 2} demo();
當eval函數執行時,在demo函數執行環境的最頂端作用域添加了變數a
這個局部環境中的a「遮蔽」了全域環境的a
最終導致程式列印2
eval()函數不僅可以修改它目前所處的作用域,甚至可以修改全域作用域
無論怎樣,它都可以在運行期間修改詞法作用域
ES5的嚴格模式對這個函數加了一些限制,我把上面的程式碼加上局部嚴格模式
var a = 1;function demo(){ 'use strict'; eval('var a = 2;'); console.log(a);// 1} demo();
我們發現這回控制台印了1
這是因為在嚴格模式下, eval()運行時擁有自己獨立的詞法作用域(省的它給執行環境的作用域鏈搗亂)
這樣其中的聲明就無法修改它所在的作用域了
這種可以動態產生程式碼的還有兩個和它很像
計時器setTimeout()和setInterval()第一個參數可以是程式碼字串
還有函式建構器new Function()的最後一個參數同樣接受程式碼字串
和eval()一樣,不要使用這種用法,這會帶來嚴重的效能問題,這個問題一會兒再說
另一個不建議使用的欺騙詞法的語法就是這個with關鍵字
with通常用作重複引用某個物件的多個屬性的捷徑
好處是可以不需要重複的引用物件本身
例如我想重複使用console物件
console.log(1);console.info(2);console.warn(3);
使用with關鍵字
with(console){ log(1); info(2); warn(3); }
#看起來with好像沒什麼問題,但看下面
function demo(obj){ with(obj){ a = 5; } }var obj1 = {a:1};var obj2 = {b:2}; demo(obj1); console.log(obj1.a);// 5demo(obj2); console.log(obj2.a);// undefinedconsole.log(a);//5 -->变量a居然泄漏到了全局环境
我們發現使用with關鍵字修改了obj1的a
但是它不僅沒有在obj2上增加a,反而產生副作用洩漏到了全局
這是因為with可以把一個物件處理為一個完全隔離的詞法作用域(放到作用域鏈的最前面)
所以在它內部產生執行a = 5;
它會向下查找作用域鏈,但沒有找到,於是在全域創建了一個a變數(沒有var 的聲明)
注意:雖然with產生了一個詞法作用域,但是with內部的正常var聲明不會被限制在這個區塊作用域中
也就是說聲明在with外部的作用域
像這樣
function demo(){ var obj = {}; with(obj){ var b = 1; console.log(b); // 1 } console.log(b); // 1} demo(); console.log(b);// Uncaught ReferenceError: b is not defined
而且with關鍵字在ES5的嚴格模式乾脆就不讓用
如果你嘗試使用你會看到這樣的錯誤:
除了eval與with之外,try-catch語句中的catch子句同樣可以修改執行環境的作用域鏈
當try程式碼區塊內發生錯誤,執行流立即跳到catch子句
隨後把異常物件推入一個可變物件並且放到作用域鏈最前面,這和with很像
一旦catch子句執行完畢,作用域鏈就會恢復原樣
但是和eval和with不同,try-catch還是相對有用,不用完全拋棄(雖然我沒用過)
欺騙詞法會產生效能問題
js引擎在編譯階段會進行效能最佳化,許多最佳化依賴於能夠根據程式碼詞法進行靜態分析
預先確定了變數和函數的定義位置,才能快速找到標識符
但是eval或with無法判斷標識符位置(存在於程式碼執行過程中,無法靜態分析)
也就是說:在eval和with面前,js引擎所有的最佳化沒有任何意義(簡直酷炫)
既然沒意義,js引擎乾脆就不優化了
這樣就導致程式運行變慢了
對於with,它還有自己獨特的效能問題…
產生了作用域,就會導致它所在的函數的所有局部變數處於第二個作用鏈物件
存取代價更高了
對於try-catch語句,如果我們想要使用,可以這樣做
try{ ...}catch(e){ handleError(e); }
在catch語句中只執行了一段程式碼,委託給一個函數用於處理錯誤
這樣沒有局部變數的存取
作用域鏈的臨時改變就不會影響效能
總結重點
#詞法作用域意味著作用域是書寫程式碼時函數宣告的位置來決定
編譯時詞法分析階段能知道所有標識符在哪裡及如何宣告
eval可以對程式碼字串進行演算,藉此在執行時修改了詞法作用域
with透過將一個物件引用當作作用域來處理,藉此在運行時創建了詞法作用域
#eval在嚴格模式下會產生獨立詞法作用域,無法修改所在作用域
with在嚴格模式下禁止使用
eval與with(還有catch)可以欺騙詞法,在執行時修改作用域鏈
eval與with致使js引擎無法在編譯階段優化作用域查找(無法靜態分析),導致程式變慢
說了這麼多,就是要告訴大家不要使用with關鍵字與eval函數~( ̄0 ̄)/
以上就是JavaScript欺騙詞法的eval、with與catch及其效能問題的內容,更多相關內容請關注PHP中文網(www.php.cn )!