天天看點

運作時的代碼串執行: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.

繼續閱讀