eval的那些事
一、事情起因
在学习javascript的时候遇见eval这个函数,很多javascript的书籍都提醒要慎用这个方法,所以到现在对eval都不是很了解。一直到前几天,在项目有遇到了一个问题,才想到去深入的了解这个javascript中最为强大的方法。
二、eval语法
eval语法其实并不复杂,它接受一个string格式的参数,然后将这个参数交给JS解释器来执行。
例如:
1 var foo = '2 + 3'; 2 eval(foo); //5 3 4 var foo = '"a" + "b"'; 5 eval(foo); //'ab' 6 eval('var a = 1'); //undefined
很显然,这里将字符串解释为javascript代码来执行了,如果这段代码可以计算出一个值,那么就将这个值返回,否则就返回undefined。如果传给eval的参数不是一个字符窜,那么eval会直接将参数输出。例如:
1 eval(2); //2 2 eval(function(){}); // function(){}
下面说一个大家都非常熟悉的情况,就是用eval方法来解析JSON字符串。现在通过AJAX从服务器上请求了一段JSON字符串,如:
1 var jsonStr = "{name: 'hupeng', company: 'AutoNavi'}";
通过eval可以将这段JSON字符串解析为对象,这样我们就可以取得其中的数据:
1 eval("(" + jsonStr + ")");
其结果会返回一个对象,包含着name和company属性。但是我们如果不加“()”,会发生什么事情,请看下面这个例子:
1 var jsonStr1 = "{name: 'hupeng'}"; 2 eval(jsonStr1); // "hupeng" 3 4 var jsonStr2 = "{name: 'hupeng', company: 'AutoNavi'}"; 5 eval(jsonStr2); // SyntaxError: Unexpected token :
为什么第一个没有抛出错误,而第二个却抛出语法错误。这是由“{}”的二义性造成的,众所周知,我们可以使用“{}”来声明对象,也可以用它来创建一个语句块。如果我们不用“()”包裹住JSON字符串,那么eval会认为传进来的是一个语句块。解释器按照语句块来解释这段字符串,由于name: 'hupeng'被认为是一个标签语句,而“,”是不可以出现在标签语句后面的,从而引发了语法错误。这也是第一个没有抛出错误,而返回'hupeng'的原因。加上了“()”,将其转化成了一个表达式,这样就可以得到我们想要的结果。
再看另外一个例子:
1 function test(){ 2 eval("var foo = 1;"); 3 alert(foo); 4 } 5 6 test(); //1 7 alert(foo); //抛出错误:foo undefined
第6行运行了test函数,弹出了1,因为通过eval方法声明了一个变量,并将其值赋为1。而第7行则抛出了一个错误:foo undefined。这说明eval方法创建的变量只存在于test函数的作用域内,在全局作用域内调用就出现了错误。
三、eval作用域
在前一个例子中,我们看到eval方法是存在作用域的,其并不会为动态执行的代码创建新的作用域,而是直接在当前作用域里执行。但是有一个很诡异的事情就是:eval和window.eval并不等同。请看下面两段代码
DEMO_1:
1 var foo = 1; 2 function test(){ 3 var foo = 2; 4 eval('foo = 3'); 5 return foo; 6 } 7 alert(test()); 8 alert(foo);
DEMO_2:
1 var foo = 1; 2 function test(){ 3 var foo = 2; 4 window.eval('foo = 3'); 5 return foo; 6 } 7 alert(test()); 8 alert(foo);
在chrome,firefox和IE9+中,DEMO_1会先后弹出3,1 。DEMO_2会先后弹出2,3。而IE678则不会对eval和window.eval作出区分,DEMO_1和DEMO_2的结果一样,都先后弹出3,1。
这说明window.eval会把动态执行的代码抛到全局作用域里执行,而eval则只是在当前作用域中执行。