首頁 >web前端 >js教程 >JavaScript欺騙詞法的eval、with與catch及其效能問題

JavaScript欺騙詞法的eval、with與catch及其效能問題

黄舟
黄舟原創
2017-02-28 14:58:471499瀏覽

正常來說,執行期上下文的作用域鍊是不會改變的 

JavaScript中的詞法作用域並不是一成不變的
(詞法作用域/靜態作用域:作用域由書寫程式碼時函數宣告位置決定)
有幾種機制是可以欺騙詞法的
它們是with()、eval()還有try-catch語句的catch子句
其中with和eval我們不應該去使用(會產生很多問題)
欺騙詞法的意思就是欺騙詞法作用域
也就是說,它們在運行時改變了作用域鏈
下面我就來談談這些可以欺騙詞法的機制

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關鍵字
with通常用作重複引用某個物件的多個屬性的捷徑
好處是可以不需要重複的引用物件本身
例如我想重複使用console物件

console.log(1);console.info(2);console.warn(3);

使用with關鍵字

with(console){    log(1);    info(2);    warn(3);
}

JavaScript欺騙詞法的eval、with與catch及其效能問題

#看起來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的嚴格模式乾脆就不讓用
如果你嘗試使用你會看到這樣的錯誤:

JavaScript欺騙詞法的eval、with與catch及其效能問題

catch

除了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 )!


#
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn