eval
是 js 中一個強大的方法。都說eval == evil
等於true
,這篇文章將研討eval
的幾個缺點和使用注意事項。javascript
太明顯了,暫不討論java
都知道 eval
比較慢,到底慢多少,本身測測看,下面是代碼(對比運行 1萬次 eval("sum++")
和 500萬次 sum++
所須要的時間)node
var getTime = function(){ // return Date.now(); return new Date().getTime() //兼容ie8 } var sum; // 測試 1萬次 eval("sum++") sum = 0; var startEval = getTime(); for(var i = 0;i<10000;i++){ eval("sum++"); } var durEval = getTime() - startEval; console.log("durEval = ",durEval,"ms"); // 測試 500萬次 sum++ sum = 0; var startCode = getTime(); for(var i = 0;i<5000000;i++){ sum++; } var durCode = getTime() - startCode; console.log("durCode = ",durCode,"ms"); //輸出結果 console.log('直接運行 sum++ 的速度約是 運行 eval("sum++") 的',(durEval * 500 / durCode).toFixed(0),'倍');
在同一臺PC上,測試3款瀏覽器和nodejs環境,結果以下: git
Chrome 73github
durEval = 236 ms durCode = 14 ms 直接運行 sum++ 的速度約是 運行 eval("sum++") 的 8429 倍
Firefox 65瀏覽器
durEval = 766 ms durCode = 167 ms 直接運行 sum++ 的速度約是 運行 eval("sum++") 的 2293 倍
IE8安全
durEval = 417ms durCode = 572ms 直接運行 sum++ 的速度約是 運行 eval("sum++") 的365倍
Nodejs 10.15.0閉包
durEval = 5 ms durCode = 14 ms 直接運行 sum++ 的速度約是 運行 eval("sum++") 的 179 倍
Chrome 的 V8 果真是王者,Firefox 在運行eval
的PK上輸給了古董IE8,node環境中eval
的表現最好(只慢100多倍)less
在做用域方面,eval
的表現讓人費解。直接調用時:當前做用域;間接調用時:全局做用域。函數
eval
被直接調用而且調用函數就是eval
自己時,做用域爲當前做用域,function
中的foo
被修改了,全局的foo
沒被修改。
var foo = 1; function test() { var foo = 2; eval('foo = 3'); return foo; } console.log(test()); // 3 console.log(foo); // 1
間接調用eval
時 執行的做用域爲全局做用域,兩個function
中的foo
都沒有被修改,全局的foo
被修改了。
var foo = 1; (function(){ var foo = 1; function test() { var foo = 2; var bar = eval; bar('foo = 3'); return foo; } console.log(test()); // 2 console.log(foo); // 1 })(); console.log(foo); // 3
使用eval
會致使內存的浪費,這是本文要討論的重點。
下面用測試結果來對比,使用eval 和 不使用eval 的狀況下,如下代碼內存的消耗狀況。
var f1 = function(){ // 建立一個f1方法 var data = { name:"data", data: (new Array(50000)).fill("data 111 data") }; // 建立一個不會被使用到的變量 var f = function(){ // 建立f方法而後返回 console.log("code:hello world"); }; return f; }; var F1 = f1();
在Chrome上查看內存使用狀況,開發者工具
->Momery
->Profiles
->Take snapshot
,給內存拍個快照。
爲了便於查找,在過濾器中輸入window
,查看當前域的window
:
能夠看到,window
佔用了68612
,2%
的內存。而後在其中找F1
變量:
F1
佔用了32
,0%
的內存。
這彷佛說明不了什麼。沒有對比就沒有傷害,下面咱們來傷害一下eval
。
修改上面的代碼,把 console.log
修改成 eval
運行:
- console.log("code:hello world"); + eval('console.log("eval:hello world");');
方法同上。在Chrome上查看內存使用狀況,開發者工具
->Momery
->Profiles
->Take snapshot
。
window
佔用了251048
,4%
的內存,其中F1
佔用了200140
,至關於總量的3%
的內存,F1.context.data
,佔用了200044
,約等於F1
的佔用量,可見這些額外的內存開銷都是來自於F1.context.data
。
使用eval時:F1
佔用了200140
,3%
的內存;
不用eval時:F1
佔用了32
,0%
的內存;
這樣的差異來自於javascript引擎的優化。在方法f1
運行時建立了data
,接着建立了一個方法f
,f
中能夠訪問到data
,但它沒有使用data
,而後f
被返回賦值給變量F1
,通過javascript引擎優化,這時data
不會被加入到閉包中,同時也沒有其餘指針指向data
,data
的內存就會被回收。然而在f
中使用了eval
後,狀況就不一樣了,eval
太過強大,致使javascript引擎沒法分辨f
會不會使用到data
,從而只能將所有的環境變量(包括data
),一塊兒加入到閉包中,這樣F1
就間接引用了data
,data
的內存就不會被回收。從而致使了額外的內存開銷。
咱們能夠進一步測試,這時在開發者工具
->Console
中輸入:
F1 = "Hello" //重設F1,這樣就沒什麼引用到data了
而後用一樣的方法查看內存,能夠發現 window
佔用的內存,從200000+
降低到了60000+
。
說到這裏,再回頭看eval
奇怪的做用域。直接調用時:當前做用域;間接調用時:全局做用域,也就能夠理解了。當間接調用時,javascript引擎不知道它是eval
,優化時就會移除不須要的變量,若是eval
中用到了那些變量,就會發生意想不到的事情。這違背了閉包的原則,變得難以理解。索性把間接調用的做用域設置爲了全局。
分析:eval
是否安全主要由數據源決定,若是數據源不安全,eval
只是提供了一種攻擊方法而已。
方案:嚴格管控數據源。
分析:eval
比直接運行慢不少倍,但主要的消耗在於編譯代碼過程,簡單項目中,不會這樣高頻率的運行eval
。
方案:低頻使用時影響不大,不要高頻使用,建議尋找替代方案。
分析:實際項目中直接調用都不多,間接調用更是少之又少。
方案:瞭解直接調用和間接調用的區別,遇到問題時不要懵逼便可。
分析:實際應用中很常見,卻不多有人會注意到內存管理,大項目中被重複使用會浪費較多的內存。
方案:優化編碼規範,使用eval
時注意那些沒有被用到局部變量。
源碼連接:github