JavaScript当中的eval函数
eval函数
eval函数接收一个由JavaScript语句组成的字符串,并且返回字符串中最后一条语句的返回值,如果最后一条语句没有返回值,那么eval函数返回undefined。如果传递给eval函数的不是字符串,那么传递什么,eval就返回什么。
调用eval函数的三种情况
当调用eval函数时,JavaScript会创建新的执行环境,总共有三种情形:
1 直接调用
直接调用时,eval函数相关的执行环境属性ThisBinding,LexicalEnvironment,VariableEnvironment的值如下:
a) ThisBinding是调用eval函数时,调用方执行环境的ThisBinding
b) LexicalEnvironment是调用eval函数时,调用方执行环境的LexicalEnvironment
c) VariableEnvironemnt是调用eval函数时,调用方执行环境的VariableEnvironment
假设有如下JavaScript代码(f在全局环境下被调用):
function f() { var i = 1; eval("var y = 2; i = 3");
alert(y);
} f();
当eval函数被调用时,执行环境栈如下图所示:
需要注意的是,当新建一个执行环境时要进行标识符绑定,绑定的标识符放到执行环境VariableEnvironment指向的Lexical Environment中。由于eval函数的VariableEnvironment与调用方(即调用eval的函数f)的VariableEnvironment指向同一个Lexical Environment,因此,在eval中声明的局部变量y被绑定到调用方的Lexical Environment中,这导致当eval函数调用结束,与eval相关的执行环境被弹出栈顶,而在eval中声明的局部变量y在函数f中仍然可以访问得到。因此,上面代码中alert会显示2,而不是报错。
2 间接调用
所谓间接调用,即将eval赋值给另一个变量后在调用,如下面代码所示:
var g = eval; g("var y = 1;");
间接调用也会创建新的执行环境,不同之处在于新执行环境的ThisBinding,LexicalEnvironment,VariableEnvironment的值不同:
a) ThisBinding为全局对象
b) LexicalEnvironment为全局执行环境的的LexicalEnvironment
c) VariableEnvironment为全局执行环境的VariableEnvironment
假如有下面的代码:
function f() { var i = 1; var gEval = eval; gEval("var y = 2; i = 3"); alert(y); } f();
当调用gEval时,执行环境栈如下图所示:
可以看到,在gEval当中声明的变量都绑定到了全局执行环境当中,需要注意的是,gEval当中的变量i并不是引用的函数f的变量i,因为从gEval的作用域链访问不到函数f的局部变量i,此时gEval中的变量i就等价于没有使用关键字var声明了一个全局变量。函数f的alert语句仍然显示2,只是此时访问的y是全局环境中的变量y。
3 严格模式下的eval
在严格模式下,eval的LexicalEnvironment,VariableEnvironment指向属于eval自己的Lexcial Environment,而不是调用方的Lexical Environment,但是ThisBinding还是调用方的ThisBinding。同时,在严格模式下如果eval直接调用,那么eval的Lexical Environment的outer指针指向调用方的Lexical Environment,否则,如果是间接调用,那么eval的Lexical Environment的outer指针指向全局环境的Lexical Environment。
假如有如下代码:
"use strict"; //使用严格模式 function f() { var i = 1; eval("var y = 2; i = 3"); alert(y); } f();
当调用eval时,执行环境栈如下图所示:
从图可以看到,eval中声明的局部变量y被绑定到自己的Lexical Environment中,eval中的i访问的是函数f声明的局部变量i。由于变量y被绑定在eval自己的Lexical Environment中,因此当eval运行结束,相关的执行环境被弹出栈顶之后,函数f是访问不到变量y的,因此alert要报错。
IE中的eval
IE9之前,无论eval是直接调用还是间接调用,eval都当成直接调用处理,如果需要有间接调用的效果,可以使用IE提供的execScript函数。
参考资料:
JavaScript权威指南
ECMA-262