本文深入解析JavaScript中eval函数的行为机制,重点探讨其在函数内部执行时受限于局部作用域的问题。通过对比IE的execScript与Firefox中window.eval的特性,提供一套兼容多浏览器的解决方案,实现在函数内部动态定义全局变量或函数,解决Ajax开发中动态代码执行的常见难题。
eval函数基本用法与返回值机制
介绍JavaScript中eval函数的基本用法。
探讨如何在函数内部执行全局代码的方法。
首先介绍eval的基本用法,内容较为简单,熟悉者可跳过。
eval函数接收一个参数s。若s不是字符串,则直接返回s;否则执行s中的语句。如果s执行的结果是一个值,则返回该值;否则返回undefined。
需特别注意对象声明语法“{}”不会返回值,必须用括号包裹才能返回结果。示例如下:
var code1='"a" + 2'; //表达式
var code2='{a:2}'; //语句
alert(eval(code1)); //->'a2'
alert(eval(code2)); //->undefined
alert(eval('(' + code2 + ')')); //->[object Object]
可见,对于对象声明语句,仅执行而不返回值。若要返回常用的“{}”对象声明语句,必须用括号括起,将其转换为表达式以返回值。这也是使用JSON进行Ajax开发的基本原理之一。示例中第二个alert输出undefined,而第三个加括号后输出了对象。
eval在函数内的作用域限制
接下来讨论本文重点:如何在函数内执行全局代码。先看一个例子:
var s='global'; //定义一个全局变量
function demo1(){
eval('var s="local"');
}
demo1();
alert(s); //->global
上述demo1函数等价于:function demo1(){var s='local';},其中定义了一个局部变量s。
因此最终输出global并不奇怪,因为局部变量和全局变量界限清晰。
仔细体会可发现,eval函数总是在调用它的上下文变量空间(也称为包或闭包)内执行,无论是变量定义还是函数定义。因此以下代码会产生函数未定义错误:
var s='function test(){return 1;}'; //一个函数定义语句
function demo2(){
eval(s);
}
demo2();
alert(test()); //->error:test is not defined
这是因为test函数在局部空间定义,仅在demo2函数内可访问,外部无法访问。
在实际Ajax开发中,有时需从服务器动态获取代码执行,以减轻初始加载负担,或执行由JavaScript自身生成的代码。
但此类动态获取代码的工作通常在函数内完成,例如:
function loadCode(){
var code=getCode();
eval(code);
}
可见eval不可能在全局空间内执行,这给开发带来诸多问题,许多人因此感到困扰。
跨浏览器全局代码执行解决方案
现已找到解决办法,同时兼容IE和Firefox,方法如下:
var X2={} //my namespace:)
X2.Eval=function(code){
if(!!(window.attachEvent && !window.opera)){
//ie
execScript(code);
}else{
//not ie
window.eval(code);
}
}
现在若要在函数内定义全局代码,可通过调用X2.Eval(code)方法。示例如下:
var s='global';
function demo3(){
X2.Eval('var s="local"');
}
demo3();
alert(s); //->'local'
可见,在demo3函数内重新定义了全局变量s="local"。
需注意X2.Eval不返回值。若需表达式求值,仍使用系统eval函数。X2.Eval设计为仅用于全局代码定义。
或许有人觉得问题易解,但发现此办法需些运气和技巧:
(1)对于IE浏览器,默认提供execScript函数用于在全局空间执行代码,知晓者不多。
(2)对于Firefox浏览器,直接调用eval函数则在调用者空间执行;若调用window.eval则在全局空间执行。知晓者更少,毕竟alert(eval==window.eval)返回true。
Firefox的eval函数特点虽奇怪,但从JavaScript规范中可找到来源:
If value of the eval property is used in any way other than a direct call (that is, other than by the explicit use of its
name as an Identifier which is the MemberExpression in a CallExpression), or if the eval property is assigned to,
an EvalError exception may be thrown.
意思大致为eval函数的执行与调用者相关,但未明确说明其执行上下文。因此IE和Firefox孰是孰非难断,知晓解决办法即可。