eval到底哪裏很差?

爲何要少用eval?

eval是 js 中一個強大的方法。都說eval == evil等於true,這篇文章將研討eval的幾個缺點和使用注意事項。javascript

目錄

  • 1、安全性
  • 2、運行效率
  • 3、做用域
  • 4、內存▲
  • 5、總結和應對方案

1、安全性

太明顯了,暫不討論java

2、運行效率

都知道 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

3、做用域

在做用域方面,eval 的表現讓人費解。直接調用時:當前做用域;間接調用時:全局做用域函數

3.1 直接調用

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

3.2間接調用

間接調用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

4、內存 ▲

使用eval會致使內存的浪費,這是本文要討論的重點。
下面用測試結果來對比,使用eval不使用eval 的狀況下,如下代碼內存的消耗狀況。

4.1 不用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,給內存拍個快照。

use_tool

爲了便於查找,在過濾器中輸入window,查看當前域的window

code_momery_1

能夠看到,window佔用了686122%的內存。而後在其中找F1變量:

code_momery_2

F1佔用了320%的內存。

這彷佛說明不了什麼。沒有對比就沒有傷害,下面咱們來傷害一下eval

4.2 使用eval的狀況

修改上面的代碼,把 console.log 修改成 eval 運行:

- console.log("code:hello world");
+ eval('console.log("eval:hello world");');

測試結果

方法同上。在Chrome上查看內存使用狀況,開發者工具->Momery->Profiles->Take snapshot

eval_momery_1

window佔用了2510484%的內存,其中F1佔用了200140,至關於總量的3%的內存,F1.context.data,佔用了200044,約等於F1的佔用量,可見這些額外的內存開銷都是來自於F1.context.data

4.3 分析

使用eval時:F1佔用了2001403%的內存;
不用eval時:F1佔用了320%的內存;

這樣的差異來自於javascript引擎的優化。在方法f1運行時建立了data,接着建立了一個方法ff中能夠訪問到data,但它沒有使用data,而後f被返回賦值給變量F1,通過javascript引擎優化,這時data不會被加入到閉包中,同時也沒有其餘指針指向datadata的內存就會被回收。然而在f中使用了eval後,狀況就不一樣了,eval太過強大,致使javascript引擎沒法分辨f會不會使用到data,從而只能將所有的環境變量(包括data),一塊兒加入到閉包中,這樣F1就間接引用了datadata的內存就不會被回收。從而致使了額外的內存開銷。

咱們能夠進一步測試,這時在開發者工具->Console 中輸入:

F1 = "Hello"  //重設F1,這樣就沒什麼引用到data了

而後用一樣的方法查看內存,能夠發現 window佔用的內存,從200000+降低到了60000+

說到這裏,再回頭看eval奇怪的做用域。直接調用時:當前做用域;間接調用時:全局做用域,也就能夠理解了。當間接調用時,javascript引擎不知道它是eval,優化時就會移除不須要的變量,若是eval中用到了那些變量,就會發生意想不到的事情。這違背了閉包的原則,變得難以理解。索性把間接調用的做用域設置爲了全局。

5、總結和應對方案

安全性

分析:eval是否安全主要由數據源決定,若是數據源不安全,eval只是提供了一種攻擊方法而已。
方案:嚴格管控數據源。

運行效率

分析:eval比直接運行慢不少倍,但主要的消耗在於編譯代碼過程,簡單項目中,不會這樣高頻率的運行eval
方案:低頻使用時影響不大,不要高頻使用,建議尋找替代方案。

做用域

分析:實際項目中直接調用都不多,間接調用更是少之又少。
方案:瞭解直接調用和間接調用的區別,遇到問題時不要懵逼便可。

內存

分析:實際應用中很常見,卻不多有人會注意到內存管理,大項目中被重複使用會浪費較多的內存。
方案:優化編碼規範,使用eval時注意那些沒有被用到局部變量。

源碼連接:github

相關文章
相關標籤/搜索