本文內容
- 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 庫比較多,安全性能好很多。
參考資料