eval到底哪里不好?

eval是 js 中一個(gè)強(qiáng)大的方法,它的作用是編譯運(yùn)行傳入字符串代碼。都說eval == evil等于true,這篇文章將研討eval的幾個(gè)缺點(diǎn)和使用注意事項(xiàng)。

目錄

  • 一、安全性
  • 二、運(yùn)行效率
  • 三、作用域
  • 四、內(nèi)存▲
  • 五、總結(jié)和應(yīng)對方案

一、安全性

太明顯了,暫不討論

二、運(yùn)行效率

都知道 eval 比較慢,到底慢多少,自己測測看,下面是代碼(對比運(yùn)行 1萬次 eval("sum++") 和 500萬次 sum++ 所需要的時(shí)間)

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");
//輸出結(jié)果
console.log('直接運(yùn)行 sum++ 的速度約是 運(yùn)行 eval("sum++") 的',(durEval * 500 / durCode).toFixed(0),'倍');

測試結(jié)果

在同一臺PC上,測試3款瀏覽器和nodejs環(huán)境,結(jié)果如下:

Chrome 73

durEval =  236 ms
durCode =  14 ms
直接運(yùn)行 sum++ 的速度約是 運(yùn)行 eval("sum++") 的 8429 倍

Firefox 65

durEval =  766 ms
durCode =  167 ms
直接運(yùn)行 sum++ 的速度約是 運(yùn)行 eval("sum++") 的 2293 倍

IE8

durEval = 417ms
durCode = 572ms
直接運(yùn)行 sum++ 的速度約是 運(yùn)行 eval("sum++") 的365倍

Nodejs 10.15.0

durEval =  5 ms
durCode =  14 ms
直接運(yùn)行 sum++ 的速度約是 運(yùn)行 eval("sum++") 的 179 倍

Chrome 的 V8 果然是王者,F(xiàn)irefox 在運(yùn)行eval的PK上輸給了古董IE8,node環(huán)境中eval的表現(xiàn)最好(只慢100多倍)
PS:筆者測試環(huán)境有限,大家有個(gè)感性認(rèn)識就好。

三、作用域

在作用域方面,eval 的表現(xiàn)讓人費(fèi)解。直接調(diào)用時(shí):當(dāng)前作用域;間接調(diào)用時(shí):全局作用域。

3.1 直接調(diào)用

eval被直接調(diào)用并且調(diào)用函數(shù)就是eval本身時(shí),作用域?yàn)楫?dāng)前作用域,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間接調(diào)用

間接調(diào)用eval時(shí) 執(zhí)行的作用域?yàn)槿肿饔糜颍瑑蓚€(gè)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

四、內(nèi)存 ▲

使用eval會導(dǎo)致內(nèi)存的浪費(fèi),這是本文要討論的重點(diǎn)。
下面用測試結(jié)果來對比,使用eval不使用eval 的情況下,以下代碼內(nèi)存的消耗情況。

4.1 不用eval的情況

var f1 = function(){          // 創(chuàng)建一個(gè)f1方法
  var data = {
    name:"data",
    data: (new Array(50000)).fill("data 111 data")
    };                        // 創(chuàng)建一個(gè)不會被使用到的變量
  var f = function(){         // 創(chuàng)建f方法然后返回
    console.log("code:hello world");
  };
  return f;
};
var F1 = f1();

測試結(jié)果

在Chrome上查看內(nèi)存使用情況,開發(fā)者工具->Momery->Profiles->Take snapshot,給內(nèi)存拍個(gè)快照。

use_tool

為了便于查找,在過濾器中輸入window,查看當(dāng)前域的window

code_momery_1

可以看到,window占用了68612,2%的內(nèi)存。然后在其中找F1變量:

code_momery_2

F1占用了32,0%的內(nèi)存。

這似乎說明不了什么。沒有對比就沒有傷害,下面我們來傷害一下eval。

4.2 使用eval的情況

修改上面的代碼,把 console.log 修改為 eval 運(yùn)行:

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

測試結(jié)果

方法同上。在Chrome上查看內(nèi)存使用情況,開發(fā)者工具->Momery->Profiles->Take snapshot

eval_momery_1

window占用了2510484%的內(nèi)存,其中F1占用了200140,相當(dāng)于總量的3%的內(nèi)存,F1.context.data,占用了200044,約等于F1的占用量,可見這些額外的內(nèi)存開銷都是來自于F1.context.data。

4.3 分析

使用eval時(shí):F1占用了2001403%的內(nèi)存;
不用eval時(shí):F1占用了32,0%的內(nèi)存;

這樣的差別來自于javascript引擎的優(yōu)化。在方法f1運(yùn)行時(shí)創(chuàng)建了data,接著創(chuàng)建了一個(gè)方法ff中可以訪問到data,但它沒有使用data,然后f被返回賦值給變量F1,經(jīng)過javascript引擎優(yōu)化,這時(shí)data不會被加入到閉包中,同時(shí)也沒有其他指針指向datadata的內(nèi)存就會被回收。然而在f中使用了eval后,情況就不同了,eval太過強(qiáng)大,導(dǎo)致javascript引擎無法分辨f會不會使用到data,從而只能將全部的環(huán)境變量(包括data),一起加入到閉包中,這樣F1就間接引用了data,data的內(nèi)存就不會被回收。從而導(dǎo)致了額外的內(nèi)存開銷。

我們可以進(jìn)一步測試,這時(shí)在開發(fā)者工具->Console 中輸入:

F1 = "Hello"  //重設(shè)F1,這樣就沒什么引用到data了

然后用同樣的方法查看內(nèi)存,可以發(fā)現(xiàn) window占用的內(nèi)存,從200000+下降到了60000+。

說到這里,再回頭看eval奇怪的作用域。直接調(diào)用時(shí):當(dāng)前作用域;間接調(diào)用時(shí):全局作用域,也就可以理解了。當(dāng)間接調(diào)用時(shí),javascript引擎不知道它是eval,優(yōu)化時(shí)就會移除不需要的變量,如果eval中用到了那些變量,就會發(fā)生意想不到的事情。這違背了閉包的原則,變得難以理解。索性把間接調(diào)用的作用域設(shè)置為了全局。

五、總結(jié)和應(yīng)對方案

安全性

分析:eval是否安全主要由數(shù)據(jù)源決定,如果數(shù)據(jù)源不安全,eval只是提供了一種攻擊方法而已。
方案:嚴(yán)格管控?cái)?shù)據(jù)源。

運(yùn)行效率

分析:eval比直接運(yùn)行慢很多倍,但主要的消耗在于編譯代碼過程,簡單項(xiàng)目中,不會這樣高頻率的運(yùn)行eval。
方案:低頻使用時(shí)影響不大,不要高頻使用,建議尋找替代方案。

作用域

分析:實(shí)際項(xiàng)目中直接調(diào)用都很少,間接調(diào)用更是少之又少。
方案:了解直接調(diào)用和間接調(diào)用的區(qū)別,遇到問題時(shí)不要懵逼即可。

內(nèi)存

分析:實(shí)際應(yīng)用中很常見,卻很少有人會注意到內(nèi)存管理,大項(xiàng)目中被重復(fù)使用會浪費(fèi)較多的內(nèi)存。
方案:優(yōu)化編碼規(guī)范,使用eval時(shí)注意那些沒有被用到局部變量。

源碼鏈接:github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)還能接受輸入的參數(shù),不同的參數(shù)會返回不同的值。 概述 函數(shù)的聲明 JavaSc...
    許先生__閱讀 490評論 0 1
  • 1 概述 1.1函數(shù)的聲明 JavaScript 有三種聲明函數(shù)的方法。 (1)function 命令 funct...
    徵羽kid閱讀 446評論 0 1
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 4,023評論 0 7
  • 沒有歲月可回頭,這只走一遭的路,我要好好走。提出以下整改方向, 王陽明說,破山中賊,人心思想統(tǒng)領(lǐng), 不怕歷練摔打。...
    淺吟低唱_可否閱讀 217評論 0 1
  • 季節(jié)過得好快,櫻花開了又落了,櫻桃熟了?;ㄩ_花落可以成為詩意的浪漫,果子的成熟又在訴說豐盈的收獲。季節(jié)的故事,實(shí)際...
    d5677b4c2f24閱讀 244評論 0 0

友情鏈接更多精彩內(nèi)容