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

posted @ 2013-10-04 16:29  船长&CAP  阅读(2677)  评论(3编辑  收藏  举报
免费流量统计软件