天天看点

运行时的代码串执行:eval与Function 运行时代码执行

两个很少用到的特性,谨慎使用!

看代码先:

var i=2;
function g2(){
	alert(i);
}

function do2() {
    var i=1;
    function	go2(){
    	alert(i);
    }
    eval("alert('eval : '+i);");
    if(window.execScript)
    	window.execScript("alert('window.execScript : '+i);");
    else if(window.eval)
    window.eval("alert('window.eval : '+i);");
    new Function("alert('new Function : '+i);")();
    setTimeout("alert('setTimeout code String : '+i);",1);
    setTimeout(g2,1);
    setTimeout("g2();",1);
    setTimeout(go2,1);
    setTimeout("alert('error is coming : ');go2();",1);
}
do2();      

结果大家可以先想想  .................

运行时代码执行

使用 eval 会造成 yui compressor 压缩代码时由于不能静态判定 eval 中的代码串变量引用而造成eval语句所在的整条作用域链中的变量都不能被简短命名,

function compressHarm(){
   var lllllllllllllllllllllllllllllllllllllllllllllll='l',a='lllllllllllllllllllllllllllllllllllllllllllllll';
   //没法实行命名简写 lllllllllllllllllllllllllllllllllllllllll
   eval('alert('+a+')');
}      

压缩后:

function compressHarm(){var lllllllllllllllllllllllllllllllllllllllllllllll="l",a="lllllllllllllllllllllllllllllllllllllllllllllll";
eval("alert("+a+")")}compressHarm();
      

所以推荐使用全局执行函数,比如全局eval,new Function或异步添加脚本执行。

PS:google closure compiler 很厉害,好像会动态执行脚本,会先执行后再优化压缩(甚至会帮你删除他认为不必要代码),比如这里的出错压缩:

function compressHarm(){eval("alert(lllllllllllllllllllllllllllllllllllllllllllllll)")}compressHarm();      

但是当你 eval 中没有变量引用时就不可思议了:

压缩前:

function compressHarm(){
   var lllllllllllllllllllllllllllllllllllllllllllllll='l',a='lllllllllllllllllllllllllllllllllllllllllllllll';
   //yui compressor 没法实行命名简写 lllllllllllllllllllllllllllllllllllllllll,没法动态分析
   eval('alert(1)');
}

compressHarm();      

压缩后:

function compressHarm(){eval("alert(1)")}compressHarm();
      

updated 2010-11-09:

更多详见:应用 closure compiler 高级模式

1.不涉及闭包的全局eval

上述例子使用 window.eval (firefox) 与 window.execScript (ie) 都不是标准规定,且存在浏览器间的不一致性。若要遵从标准,适当的做法为 2,3。

2.使用 new Function

由于 new Function 生成函数时,会把全局环境作为生成函数的 [[Scope]] 属性,则不存在作用链引用问题。

1,2 相对于 3 为同步调用,并可将调用处的变量作为参数传递,来平衡 eval 的变量引用以及作用域链引用问题,而对于 2 更因为 new Function 可以设定函数参数,则可传对象引用,对于 1,由于是执行代码串,则只能将变量序列化后传过去。

//避免 eval 的作用域链引用问题,主要便于压缩
function test(){
 var a={},b=1;
  //只能变量序列化
  if(window.execScript) {
       window.execScript("alert("+b+")");
   }else{
        window.eval("alert("+b+")")
   }
   
   //可传任意 
   (new Function("par,obj","alert(par);alert(obj);"))(b,a);

}      

setTimeout/setInterval :如上例所示参数为字符串时,实际上就是 new Function 不设置函数参数的异步情况。

3. 在head中动态 异步添加内联脚本 ( body 中需要插到第一个子结点前,否则IE6 在 DOM ready 前会有问题:abort error)

<head>
<script>alert("inline script");</script>
</head>      

而关于脚本的具体动态添加则又有区别:

标准浏览器用 script.appendChild( document.createTextNode( data ) );即可

而 ie 则需用 script.text = data;(标准浏览器也可以)

于是得到下列代码:(From Jquery)

// Evalulates a script in a global context
	globalEval: function( data ) {
		if ( data && rnotwhite.test(data) ) {
			// Inspired by code by Andrea Giammarchi
			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
			var head = document.getElementsByTagName("head")[0] || document.documentElement,
				script = document.createElement("script");

			script.type = "text/javascript";

			if ( jQuery.support.scriptEval ) {
				script.appendChild( document.createTextNode( data ) );
			} else {
				script.text = data;
			}

			// Use insertBefore instead of appendChild to circumvent an IE6 bug.
			// This arises when a base node is used (#2709).
			head.insertBefore( script, head.firstChild );
			head.removeChild( script );
		}
	},      

注意 3 的方式不能同步调用获得返回结果。

PS:附录 ecmascript262-5th ,摘录

10.1 Types of Executable Code

There are three types of ECMAScript executable code:

•  Global code is source text that is treated as an ECMAScript  Program.

•  Eval code is the source text supplied to the built-in eval function.

•  Function code is source text that is parsed as part of a  FunctionBody.

10.4.1 Entering Global Code

The following steps are performed when control enters the execution context for global code:

1.  Initialize the execution context using the global code as described in 10.4.1.1.

2.  Perform Declaration Binding Instantiation as described in 10.5 using the global code.

10.4.1.1 Initial Global Execution Context

The following steps are performed to initialize a global execution context for ECMAScript code C:

1.  Set the VariableEnvironment to the Global Environment.

2.  Set the LexicalEnvironment to the Global Environment.

3.  Set the ThisBinding to the global object.

10.4.2 Entering Eval Code

The following steps are performed when control enters the execution context for eval code:

1.  If there is no calling context or if the eval code is not being evaluated by a direct call (15.1.2.1.1) to the eval

function then,

a.  Initialize the execution context as if it was a global execution context using the eval code as C as

described in 10.4.1.1.

2.  Else,

a.  Set the ThisBinding to the same value as the ThisBinding of the calling execution context.

b.  Set the LexicalEnvironment to the same value as the LexicalEnvironment of the calling execution

context.

c.  Set the VariableEnvironment to the same value as the VariableEnvironment of the calling execution

context.

15.3.2.1 new Function (p1, p2, … , pn, body)

Return a new Function object created as specified in 13.2 passing P as the FormalParameterList and body as

the FunctionBody. Pass in the Global Environment as the Scope parameter and strict as the Strict flag.

继续阅读