标准的JS里,eval和window.eval属于不同的语法!
最近寫的一個腳本里需要在全局下執行代碼。這個例子以前見到過,在IE6,7,8下用window.exeScript方法,其他則調用window.eval方法即可。
?
看似很簡單,運行也正確。eval聲明的a只有內部可見;window.eval聲明的b全局可見。?
<script>function A()
{
eval("var a=1");
window.eval("var b=1");
}
A();
alert(typeof a); //undefined
alert(typeof b); //number
</script>
?
不過事后回想起來感覺有些詭異。eval和window.eval不就同個函數嗎,為什么加上window.意義就不同了呢?如果說eval內部判斷了當前this,那么eval和window.eval執行時,this都是指向window。
?
如果用變量p=window.eval,那么p()和window.eval()還一樣嗎?
<script>function A()
{
var p = window.eval;
alert(p === window.eval); //true
alert(p === eval); //true
eval("var a=1");
p("var b=1");
}
A();
alert(typeof a); //undefined
alert(typeof b); //number
</script>
?
經測試,p()和window.eval()的效果完全一樣,都是在全局執行。并且p === window.eval 和 p === eval 同時成立!這顯然很奇怪,不過還有更奇怪的事在后面!如果我們用q=eval,結果完全出人意料:
<script>function A()
{
var p = window.eval;
var q = eval;
p("var a=1");
q("var b=1");
}
A();
alert(typeof a); //number
alert(typeof b); //number
</script>
?
居然顯示的都是number!也就是說指向eval的p,實際效果卻是window.eval。他們都是在全局執行代碼!
為了驗證是否是引用上的區別,我們再做一次測試:?
<script>function A()
{
var q = eval;
q("var b=1");
eval("var a=1");
}
A();
alert(typeof a); //undefined
alert(typeof b); //number
</script>
?
明顯,eval執行的a被留在了內部,而q=eval的b卻是全局的!
于是我推測,標準JavaScript下的eval,也許和this一樣,既是關鍵字,也是一個變量(函數變量)。如果是當關鍵字調用的話,即字面上的eval(),那么在當前的上下文里執行;否則,即通過變量引用調用的話,就在全局上執行。這樣就可以解釋 window.eval 和 eval 的區別了:window.eval僅僅是window對象里的一個叫eval的屬性,一個指向eval函數的屬性。和window.eval2,window.eval3一樣,僅僅一個屬性,并非字面上的eval。
為了驗證這個猜測,我查看了 FireFox 的腳本引擎源碼。其中eval真正的執行部分定義在 jsobj.cpp 中的 EvalKernel 里:
boolEvalKernel(JSContext *cx, uintN argc, Value *vp, EvalType evalType, JSStackFrame *caller,
JSObject *scopeobj)
{
...
/*
* Per ES5, indirect eval runs in the global scope. (eval is specified this
* way so that the compiler can make assumptions about what bindings may or
* may not exist in the current frame if it doesn't see 'eval'.)
*/
uintN staticLevel;
if (evalType == DIRECT_EVAL) {
staticLevel = caller->script()->staticLevel + 1;
} else {
/* Pretend that we're top level. */
staticLevel = 0;
JS_ASSERT(scopeobj == scopeobj->getGlobal());
JS_ASSERT(scopeobj->isGlobal());
}
...
}
顯然,你發現這個函數里有個叫 evalType 的參數,正是這個參數,決定了eval的是否在全局運行。如果是 DIRECT_EVAL,staticLevel 就是當前的上下文;否則 staticLevel=0,就是全局。
順藤摸瓜,我們又在jsinterp.cpp里發現以 DIRECT_EVAL 模式調用 EvalKernel:
boolDirectEval(JSContext *cx, JSFunction *evalfun, uint32 argc, Value *vp)
{
...
JSObject *scopeChain =
GetScopeChainFast(cx, caller, JSOP_EVAL, JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
if (!scopeChain || !EvalKernel(cx, argc, vp, DIRECT_EVAL, caller, scopeChain))
return false;
cx->regs->sp = vp + 1;
return true;
}
而調用DirectEval的地方,正是定義關鍵字的區域:
}END_CASE(JSOP_NEW)
BEGIN_CASE(JSOP_EVAL)
{
argc = GET_ARGC(regs.pc);
vp = regs.sp - (argc + 2);
if (!IsFunctionObject(*vp, &callee))
goto call_using_invoke;
newfun = callee->getFunctionPrivate();
if (!IsBuiltinEvalFunction(newfun))
goto call_using_invoke;
if (!DirectEval(cx, newfun, argc, vp))
goto error;
}
END_CASE(JSOP_EVAL)
BEGIN_CASE(JSOP_CALL)
BEGIN_CASE(JSOP_FUNAPPLY)
BEGIN_CASE(JSOP_FUNCALL)
{
?
很明顯,eval 和 new 他們一樣,都當關鍵字處理了。也就是說,只有在代碼里出現“eval(...)”這幾個字的時候,才會傳入DIRECT_EVAL,即在當前上下文內執行。
可見,ECMA-262的eval有著關鍵字的特性!但并非是真正的關鍵字,因為關鍵字是不可以作為對象屬性名出現的,例如window.this,window.var是錯誤的語法。但eval可以。
?
那假如在上下文里聲明了一個叫eval的變量,并且指向window.eval。那么又會如何呢?
<script>function A()
{
var eval = window.eval;
eval("var a=1");
eval("var b=1");
}
A();
alert(typeof a); //undefined
alert(typeof b); //undefined
</script>
?
為什么是undefined呢?因為此 eval 就是原 eval 嘛~ 都是在內部執行了。(Chrome例外,當作eval的引用對待了,結果是number)
但也不代表eval不能被覆蓋:
<script>eval = alert;
function A()
{
eval("var a=1"); //var a=1
window.eval("var b=1"); //var b=1
}
A();
</script>
正常彈出兩個對話框。
可見,在上下文環境中,eval只要保持指向原始的那個函數,沒被覆蓋,就有著關鍵字的特征;否則就是一個叫eval的變量了。jsinterp.cpp中BEGIN_CASE(JSOP_EVAL){}里的代碼也說明了這點。
p
總結
以上是生活随笔為你收集整理的标准的JS里,eval和window.eval属于不同的语法!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: S3C2410时钟部分总结
- 下一篇: Windows phone 应用开发[2