你可能不知道的eval的妙用

前言

eval() 是 JavaScript 中一个非常有用的函数,它可以一段代码字符串动态执行。然而各种编码规范和最佳实践都强烈抵制 eval,
几乎将 eval 打入了死牢,大牛 Douglas Crockford 也在《JavaScript 语言精粹》一书中将 eval 视为 JavaScript 中糟粕。

eval 是什么

在分析 eval 的利弊前,首先来认识一下它。在不清楚一项技术的情况下,就对它做出武断地评价,是有失公允的。

eval 是全局对象上的一个函数,会把传入的字符串当做 JavaScript 代码执行。如果传入的参数不是字符串,它会原封不动地将其返回。eval 分为直接调用和间接调用两种,通常间接调用的性能会好于直接调用。

直接调用时,eval 运行于其调用函数的作用域下;

1
2
3
4
5
6
7
var context = 'outside';
(function(){
var context = 'inside';
returneval('context');
})();

// return 'inside'

而间接调用时,eval 运行于全局作用域。

1
2
3
4
5
6
7
8
9
10
11
12
var context = 'outside';
(function(){
var context = 'inside';
geval = eval;
return geval('context');

// 下面两种也属于间接调用
// return eval.call(null, 'context');
// return (1, eval)('context');
})();

// return 'outside'

因此,间接调用时,eval 并不会修改调用函数作用域内的任何东西。JS 解释器有 fast path 和 slow path 两种模式,当直接调用 eval 时,解释器处于 slow path。因为此时作用域是不可控的,需要监听整个作用域,不能应用 v8 的一些编译优化,相应的编译效率也会比 fast path 低。

为什么不用 eval

大家抵制 eval 的原因主要是以下几个原因:

  1. 降低性能。具体原因上文已经提到了,网上一些文章甚至说 eval() 会拖慢性能 10 倍。
  2. 安全问题。因为它的动态执行特性,给被求值的字符串赋予了太大的权力,于是大家担心可能因此导致 XSS 等攻击。
  3. 调试困难。eval 就像一个黑盒,其执行的代码很难进行断点调试。

鉴于以上各种原因,很多人说 eval 是 evil(魔鬼)。另外,eval 还有一些难兄难弟,比如 new Function, setTimeout, setInterval。它们也具备执行一段代码字符串的能力。
究其本质原因,还是因为 JS 赋予这个方法的权限太大了,作为新手很难驾驭它,如果对 eval 没有很好地理解,很容易写出问题来。这有点像 C 语言中 goto 语句,同样是因为权限太大而被封杀的典范。

被误解的 eval

事实上,eval 一直在被误解,它可能是最强大的一个 JavaScript 函数,但却因为一些人的误用,而被开发者们打入了冷宫。

场景:动态接收一个函数名,运行结果
1
2
3
4
5
6
7
8
9
10
11
function test(){ 
console.log(1);
}

function test2(){
console.log(2);
}

['test']() // VM4065:1 Uncaught TypeError: ["test"] is not a function
eval('test')() // 1
eval('test2')() // 2