1 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,而第三個加了括弧後輸出的是語句表示的對象。
►現在來說本文的重點,如何在函數內執行全域代碼。為了說明這個問題,先看一個例子:
var s='global'; //定義一個全域變數
function demo1(){
eval('var s="local"');
}
demo1();
alert(s); //->global
很好理解,上面的demo1函數等價於:function demo1(){var s='local';},其中定義了一個局部變數s。
所以最後的輸出是global並不是什麼奇怪的事情,畢竟大家都能很清楚的區分局部變數和全域變數。
仔細體會一下,可以發現eval函數的特點,它總是在調用它的上下文變數空間(也稱為:包,closure)內執行,無論是變數定義還是函數定義都是如此,所以如下的代碼會產生函數未定義的錯誤:
var s='function test(){return 1;}'; //一個函數定義語句
function demo2(){
eval(s);
}
demo2();
alert(test()); //->error:test is not defined
這是因為test函數在局部空間定義,demo2函數內可以訪問到,外面就訪問不到了。
2 效率問題:
Simon Willison 認為:雖然Javascript提供了把動態字串作為指令碼代碼來執行的 eval() 函數,為代碼的編寫提供了很多的便利,但同時也導致了代碼執行效率的低下以及難以維護。通常來說,只有設計存在缺陷才會不得不用 eval() 來彌補這些缺陷。
對於 eval() 的使用最常見的錯誤是用來動態擷取某個對象/子物件及其屬性。假設你的代碼中有一個對象foo,其屬性將在運行時被決定。你可能會想用eval來解決這個問題:
1. var property = 'bar';
2.
3. var value = eval('foo.' + property);
4.
這樣做固然可以達到預期的效果,但這會導致JavaScript每次運行這段代碼的時候都切換到解譯器模式來重新解釋這段動態構建的代碼,其運行效率大大降低。實際上以上情況你可以完全迴避使用eval():
1. var property = 'bar';
2.
3. var value = foo[property];
4.
另一方面,使用 eval() 也可能會導致一些潛在的安全隱患。這一點對於任何語言來說都是如此。當你通過頁面的GET/POST參數來擷取資料並 eval() 之的時候,就有可能被惡意使用者利用來注入非法代碼進行非預期的操作。所以,無論是從執行效率、代碼維護成本還是應用安全性角度考慮,對於 eval() 的使用都必須謹慎。回想自己曾經寫過的那些代碼,有段時間裡面 eval() 簡直被我給用濫了 -- 最直接的後果就是給接手的其他程式員造成了不小的麻煩。要弄懂一段動態組合出來的字串到底被 eval() 之後是用來幹什麼的的確不像結構清晰的OO代碼那麼容易。
結論:有“eval”的代碼比沒有“eval”的代碼要慢上 100 倍以上。
主要原因是:JavaScript 代碼在執行前會進行類似“先行編譯”的操作:首先會建立一個當前執行環境下的使用中的物件,並將那些用 var 申明的變數設定為使用中的物件的屬性,但是此時這些變數的賦值都是 undefined,並將那些以 function 定義的函數也添加為使用中的物件的屬性,而且它們的值正是函數的定義。但是,如果你使用了“eval”,則“eval”中的代碼(實際上為字串)無法預先識別其上下文,無法被提前解析和最佳化,即無法進行先行編譯的操作。所以,其效能也會大幅度降低。