为何要罕用eval?
eval
是 js 中一个壮大的要领。都说eval == evil
即是true
,这篇文章将钻研eval
的几个瑕玷和运用注重事项。
目次
- 一、平安性
- 二、运转效力
- 三、作用域
- 四、内存▲
- 五、总结和应对计划
一、平安性
太显著了,暂不议论
二、运转效力
都晓得 eval
比较慢,究竟慢若干,自身测测看,下面是代码(对照运转 1万次 eval("sum++")
和 500万次 sum++
所须要的时候)
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环境,效果以下:
Chrome 73
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多倍)
三、作用域
在作用域方面,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
四、内存 ▲
运用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
,给内存拍个快照。
为了便于查找,在过滤器中输入window
,检察当前域的window
:
能够看到,window
占用了68612
,2%
的内存。然后在个中找F1
变量:
F1
占用了32
,0%
的内存。
这好像申明不了什么。没有对照就没有危险,下面我们来危险一下eval
。
4.2 运用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
。
4.3 剖析
运用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