eval() 是 JavaScript 中一個很是有用的函數,它能夠一段代碼字符串動態執行。然而各類編碼規範和最佳實踐都強烈抵制 eval,幾乎將 eval 打入了死牢,大牛 Douglas Crockford 也在《JavaScript 語言精粹》一書中將 eval 視爲 JavaScript 中糟粕。這篇文章將帶你們從新認識這個函數,知道爲何不用它,以及爲何不得不用它。node
在分析 eval 的利弊前,首先來認識一下它。在不清楚一項技術的狀況下,就對它作出武斷地評價,是有失公允的。json
eval 是全局對象上的一個函數,會把傳入的字符串當作 JavaScript 代碼執行。若是傳入的參數不是字符串,它會原封不動地將其返回。eval 分爲直接調用和間接調用兩種,一般間接調用的性能會好於直接調用。小程序
直接調用時,eval 運行於其調用函數的做用域下;瀏覽器
var context = 'outside';
(function(){
var context = 'inside';
return eval('context');
})();
// return 'inside'
複製代碼
而間接調用時,eval 運行於全局做用域。安全
var context = 'outside';
(function(){
var context = 'inside';
geval = eval;
return geval('context');
// 下面兩種也屬於間接調用
// return eval.call(null, 'context');
// return (1, eval)('context');
})();
// return 'outside'
複製代碼
所以,間接調用時,eval 並不會修改調用函數做用域內的任何東西。JS 解釋器有 fast path 和 slow path 兩種模式,當直接調用 eval 時,解釋器處於 slow path。由於此時做用域是不可控的,須要監聽整個做用域,不能應用 v8 的一些編譯優化,相應的編譯效率也會比 fast path 低。bash
你們抵制 eval 的緣由主要是如下幾個緣由:框架
1.下降性能。具體緣由上文已經提到了,網上一些文章甚至說 eval() 會拖慢性能 10 倍。dom
2.安全問題。由於它的動態執行特性,給被求值的字符串賦予了太大的權力,因而你們擔憂可能所以致使 XSS 等攻擊。ide
3.調試困難。eval 就像一個黑盒,其執行的代碼很難進行斷點調試。函數
鑑於以上各類緣由,不少人說 eval 是 evil(魔鬼)。另外,eval 還有一些難兄難弟,好比 new Function, setTimeout, setInterval。它們也具有執行一段代碼字符串的能力。 究其本質緣由,仍是由於 JS 賦予這個方法的權限太大了,做爲新手很難駕馭它,若是對 eval 沒有很好地理解,很容易寫出問題來。這有點像 C 語言中 goto 語句,一樣是由於權限太大而被封殺的典範。
事實上,eval 一直在被誤解,它多是最強大的一個 JavaScript 函數,但卻由於一些人的誤用,而被開發者們打入了冷宮。接下來,我來根據上述被質疑最多的幾個點,給出一點本身的見解。
關於 eval 會拖慢性能 10 倍這個點,出自 Mozila 工程師的演講 「Know Your Engines - How to make your JavaScript Fast」。
這是一個發佈於 2011 年的演講,時至今日,JS 引擎已作了各類優化。咱們來測試如今的 JS 引擎中,eval 的實際性能。依然使用上圖做爲測試用例,測試環境爲 node v8.11.1,設 N 的值爲 10000。
Benchmark 跑出的數據來看,當 N = 10000 時,用了 eval 的 function 執行性能,相比沒有 eval 的狀況,慢了 3 倍多。 將 N 的值設爲 1000000,eval 的性能降低到 8 倍。
從測試結果可知,eval 的確會拖慢函數執行性能,並且隨着函數規模增大,性能也越慢。可是在通常狀況下(N < 1000000),性能差別並無 10 倍那麼誇張。
關於 eval 會致使 XSS 攻擊這點,問題並不在 eval,而在數據源。若是數據源自己就是不可靠的,即使你不用 eval,也可能出現 XSS。
至於第三點,eval 代碼的確調試起來比較麻煩,但也不是徹底沒有辦法。能夠在 eval 建立的代碼末尾添加一行 "//@ sourceURL=name" 就能夠給這段代碼命名(瀏覽器會特殊對待這種特殊形式的註釋),這樣它就會出如今 Sources 面板上,而後就能夠設置斷點調試了。
雖然你們嘴上說不要用,可是 eval 用起來倒是真香。
<div data-eval="data.count = data.count + 1">
{{data.count}}
</div>
複製代碼
渲染出來的結果是 eval 計算後的值。
不少庫和框架都用了 eval 實現各類黑魔法。早期的有用 eval 解析 json 的,好比 Douglas Crockford 的 json2.js(真香!)。到後來,各類 MVVM 框架也用 new Function 這個 eval 的好基友,來實現模板內嵌表達式的計算,好比 Vue 和 avalon。要達到的效果和筆者上面介紹的例子大體相同,不一樣的是這些 MVVM 框架還須要先解析模板,提取出 new Function 的參數。
甚至不能用 eval 的時候,也要本身造一個 eval 出來。好比小程序上就不能使用 eval 和 new Function,那麼若是想動態注入並執行代碼的話,須要繞一個大彎,從編譯原理出發,自行實現一個 JS parser。
關於 eval,筆者我的的見解是,你能夠不去用它,但要去了解它。寫這篇文章的目的也不是爲了推薦你們使用 eval。就平時的業務開發而言,eval 幾乎沒有用武之地。但在一些特殊場合,eval 就像一枚核彈,無往不利。
若有問題或錯誤之處,歡迎在留言中討論。
參考連接:
1.Global eval. What are the options?
2.Knockout, Vue 和 AvalonJS 等 MVVM 框架實現中是否用到 eval 或 Function?
3.eval() isn’t evil, just misunderstood
4.A new V8 is coming, Node.js performance is changing.
V8: Behind the Scenes (February Edition feat. A tale of TurboFan)