Javascript是一种解释性语言,而并非编译性,它不能编译成二进制文件。
理解动态执行与闭包的概念
动态执行:javascript提供eval()函数,用于动态解释一段文本,并在当前上下文环境中执行。
首先我们需要理解的是eval()方法它有全局闭包和当前函数的闭包,比如如下代码,大家认为会输出什么呢?
var i = 100; function myFunc() { var i = 'test'; eval('i = "hello."'); } myFunc(); alert(i); // 100
首先我们来看看先定义一个变量i=100,然后调用myFunc这个函数,然后修改局部变量i,使他从值 'test'变成'hello', 但是我们知道eval的含义是立即执行一段文本的含义;因此上面的代码我们可以写成如下代码:
var i = 100; function myFunc() { var i = 'test'; (function(){ return (i = "hello."); })(); } myFunc(); alert(i); // 100
这样就很明显了,执行myFunc()这个方法后,i的值从test变为hello的值,但是由于是闭包,i的值为hello,它不能被外部使用,所以浏览器打印的都是100值;
我们都知道eval()是javascript的全局对象Global提供的方法,而如果要访问Global对象的方法,可以通过宿主对象-在浏览器中是window来提供;按道理来说,下面的代码应该也是输出100;如下:
var i = 100; function myFunc() { var i = 'test'; window.eval('i="hello."'); } myFunc(); alert(i);
然后不幸的是:在IE下不管是window.eval()还是eval()方法输出的都是100;但是在标准浏览器下使用window.eval(),输出的是hello,使用eval()方法的输出的是100; 因为IE下使用的是JScript引擎的,而标准浏览器下是SpiderMonkey Javascript引擎的,正是因为不同的javascript引擎对eval()所使用的闭包环境的理解并不相同。
理解eval使用全局闭包的场合
如下代码:
var i = 100; function myFunc() { var i = 'test'; window.eval('i="hello."'); } myFunc(); alert(i);
在标准浏览器下,打印的是hello,但是在IE下打印的是100;如果使用如下代码:
var i = 100; function myFunc() { var i = 'test'; //window.eval('i="hello."'); eval.call(window,'i="hello"'); } myFunc(); alert(i);
也是一样的,也是给eval方法提供一种访问全局闭包的能力;但是在IE下Jscript的eval()没有这种能力,IE下一只打印的是100;不过在IE下可以使用另一种方法得到一个完美的结果,window.execScript()方法中执行的代码总是会在全局闭包中执行,如下代码:
var i = 100; function myFunc() { var i = 'test'; window.execScript('i="hello."'); //eval.call(window,'i="hello"'); } myFunc(); alert(i); // 打印hello
JScript()引擎使用execScript()来将eval在全局闭包与函数闭包的不同表现分离出来,而Mozilla的javascript引擎则使用eval()函数的不同调用形式来区分它们。二者实现方法有不同,但是可以使用不同的方式实现全局闭包;
理解eval()使用当前函数的闭包
一般情况下,eval()总是使用当前函数的闭包,如下代码:
var i = 100; function myFunc() { var i = 'test'; eval('i="hello."'); } myFunc(); alert(i); // 100
如上代码:因为eval作用与是函数内的代码,所以输出的是全局变量i等于100;
eval()总是被执行的代码文本视为一个代码块,代码块中包含的是语句,复合语句或语句组。
我们可以使用如下代码取得字符串,数字和布尔值;
eval('true'); // true
eval('"this is a char"'); // string
eval('3'); // 数字3
但是我们不能使用同样的方法取得一个对象;如下代码:
eval('{name:"MyName",value:1}');
如上代码会报错;如下:Uncaught SyntaxError: Unexpected
其实如上那样写代码,{name:"MyName",value:1},eval会将一对大括号视为一个复合语句来标识,如下分析:
第一个冒号成了 “标签声明”标示符。
{“标签声明”的左操作数}name成了标签。
MyName成了字符串直接量;
Value成了变量标示符。
对第二个冒号不能合理地作语法分析,出现语法分析期异常;
如果我们只有这样一个就不会报错了,如下代码:
eval('{name:"MyName"}')
输出"MyName";
那如果我们想要解决上面的问题要如何解决呢?我们可以加一个小括号包围起来,使其成为一个表达式语句,如下代码:
eval('({name:"MyName",value:1})')
输出一个对象Object {name: "MyName", value: 1}
但是如下的匿名函数加小括号括起来在IE下的就不行了,如下代码:
var func = eval('(function(){})');
alert(typeof func); // IE下是undefined
在标准浏览器chrome和firefox是打印function,但是在IE下的JScript引擎下打印的是undefined,在这种情况下,我们可以通过具名函数来实现;如下:
eval('function func(){}');
alert(typeof func); // 打印是function
我们使用eval时候,最常见的是ajax请求服务器端返回一个字符串的格式的数据,我们需要把字符串的格式的数据转换为json格式;如下代码:
// 比如服务器返回的数据是如下字符串,想转换成json对象如下:
var data = '{"name":"Mike","sex":"女","age":"29"}';
console.log(eval("("+data+")"));
打印Object {name: "Mike", sex: "女", age: "29"} 就变成了一个对象;
// 或者直接如下 ,都可以
console.log(eval("("+'{"name":"Mike","sex":"女","age":"29"}'+")"));
我们还需要明白的是使用eval或者with语句,他们都会改变作用域的问题,比如使用eval如下代码:
var i = 100; function myFunc(name) { console.log('value is:'+i); // 100 eval(name); console.log('value is:'+i); // 10 } myFunc('var i = 10;');
如上代码,第一次执行的是100,第二次调用eval()方法,使作用域变成函数内部了,因此i变成10了;
理解动态方法调用(call与apply)