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è)快照。

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

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

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。

window占用了251048,4%的內(nèi)存,其中F1占用了200140,相當(dāng)于總量的3%的內(nèi)存,F1.context.data,占用了200044,約等于F1的占用量,可見這些額外的內(nèi)存開銷都是來自于F1.context.data。
4.3 分析
使用eval時(shí):F1占用了200140,3%的內(nèi)存;
不用eval時(shí):F1占用了32,0%的內(nèi)存;
這樣的差別來自于javascript引擎的優(yōu)化。在方法f1運(yùn)行時(shí)創(chuàng)建了data,接著創(chuàng)建了一個(gè)方法f,f中可以訪問到data,但它沒有使用data,然后f被返回賦值給變量F1,經(jīng)過javascript引擎優(yōu)化,這時(shí)data不會被加入到閉包中,同時(shí)也沒有其他指針指向data,data的內(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