JavaScript 為什么不要使用 eval


本文內容

  • eval
  • 隱藏的 eval
  • 安全問題
  • 結論
  • 參考資料

 

eval


eval 函數是一個高等級的函數,它與任何對象都無關。其參數,如果是一個字符串表達式,那么該函數計算表達式的值;如果是一個 JavaScript 語句, 則執行。通常用在一些需要動態執行的代碼中。

var foo = 1;
function test() {
    var foo = 2;
    eval('foo = 3');
    return foo;
}
test(); // 3
foo; // 1

但是,eval 只在被直接調用並且調用函數就是 eval 本身時,才在當前作用域中執行。

var foo = 1;
function test() {
    var foo = 2;
    var bar = eval;
    bar('foo = 3');
    return foo;
}
 
test(); // 2
foo; // 3

上面的代碼等價於在全局作用域中調用 eval,和下面兩種寫法效果一樣:

寫法一:直接調用全局作用域下的 foo 變量

var foo = 1;
function test() {
    var foo = 2;
    window.foo = 3;
    return foo;
}
 
test(); // 2
foo; // 3

寫法二:使用 call 函數修改 eval 執行的上下文為全局作用域

var foo = 1;
function test() {
    var foo = 2;
    eval.call(window, 'foo = 3');
    return foo;
}
 
test(); // 2
foo; // 3

在任何情況下,我們都應該避免使用 eval 函數。99.9% 使用 eval 的場景都有不使用 eval 的解決方案。

 

隱藏的 eval


定時函數 setTimeout 和 setInterval 都可以接受字符串作為第一個參數。但是這個特性絕對不要使用,因為它們在內部使用了 eval。

function foo() {
    // 將會被調用
}
 
function bar() {
    function foo() {
        // 不會被調用
    }
    setTimeout('foo()', 1000);
}
bar();

由於 eval 在這種情況下不是被直接調用,因此傳遞到 setTimeout 的字符串會自全局作用域中執行; 因此,上面的回調函數使用的不是定義在 bar 作用域中的局部變量 foo。

建議不要在調用定時器函數時,為了向回調函數傳遞參數而使用字符串的形式。

function foo(a, b, c) { }
 
// 不要這樣做
setTimeout('foo(1,2, 3)', 1000)
 
// 可以使用匿名函數完成相同功能
setTimeout(function () {
    foo(a, b, c);
}, 1000)

 

安全問題


eval 也存在安全問題,因為它會執行任意傳給它的代碼, 在代碼字符串未知或者是來自一個不信任的源時,絕對不要使用 eval 函數。

eval 一般也較慢,因為它必須調用 JS 解釋器,而很多其他構造方法都被現代 JS 引擎所優化。

jQuery 1.10.2 實現 globalEval 代碼段,如下所示:

// Evaluates a script in a global context
// Workarounds based on findings by Jim Driscoll
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
globalEval: function( data ) {
    if ( data && jQuery.trim( data ) ) {
        // We use execScript on Internet Explorer
        // We use an anonymous function so that context is window
        // rather than jQuery in Firefox
        ( window.execScript || function( data ) {
            window[ "eval" ].call( window, data );
        } )( data );
    }
},

IE 使用 window.execScript 使腳本脫離當前閉包,在全局域內運行;Firefox 則使用 window.eval 來脫離當前閉包,直接使用 eval 在當前閉包運行。

jQuery 2.0.3 實現 globalEval 代碼段,若是 code 標記為嚴格模式,則采用注入方式;否則采用 eval,如下所示:

// Evaluates a script in a global context
globalEval: function( code ) {
    var script,
            indirect = eval;
 
    code = jQuery.trim( code );
 
    if ( code ) {
        // If the code includes a valid, prologue position
        // strict mode pragma, execute code by injecting a
        // script tag into the document.
        if ( code.indexOf("use strict") === 1 ) {
            script = document.createElement("script");
            script.text = code;
            document.head.appendChild( script ).parentNode.removeChild( script );
        } else {
        // Otherwise, avoid the DOM node creation, insertion
        // and removal by using an indirect global eval
            indirect( code );
        }
    }
},

ext js 1.6 關於 eval 的代碼段,如下所示:

/**
 * Compiles the template into an internal function, eliminating the RegEx overhead.
 * @return {Ext.Template} this
 */
compile : function(){
    var me = this,
        sep = Ext.isGecko ? "+" : ",";
 
    function fn(m, name){
        name = "values['" + name + "']";
        return "'"+ sep + '(' + name + " == undefined ? '' : " + name + ')' + sep + "'";
    }
 
    eval("this.compiled = function(values){ return " + (Ext.isGecko ? "'" : "['") +
         me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
         (Ext.isGecko ?  "';};" : "'].join('');};"));
    return me;
},

 

結論


絕對不要使用 eval,任何使用它的代碼都會在它的工作方式,性能和安全性方面受到質疑。如果一些情況必須使用到 eval 才能正常工作,首先它的設計會受到質疑,這不應該是首選的解決方案, 一個更好的不使用 eval 的解決方案應該得到充分考慮並優先采用。

只要知道寫 JavaScript 代碼當使用 eval 時,存在這個問題就行,畢竟現在直接用 JavaScript 庫比較多,安全性能好很多。

 

參考資料


 

下載 Demo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM