eval(“1+2”),-> 3
動態判斷原始碼中的字串是一種很強大的語言特性,幾乎沒有必要在實際中應用。如果你使用了eval(),你應當仔細考慮是否真的需要使用它。
一、eval()是一個函數還是一個運算子
eval()是一個函數,但由於它已經被當成運算子來對待了。。JavaScript語言的早期版本定義了eval函數,現代JavaScript解譯器進行了大量的程式碼分析和最佳化。而eval的問題在於,用於動態執行的代碼通常來講不能分析,換句話說,如果一個函數調用了eval,那麼解譯器將無法對這個函數做進一步最佳化,而將eval定義為函數的另一個問題是,它可以被賦予其他的名字,var f=eval;那麼解譯器就無法放心的最佳化任何調用了f()的函數。而當eval是一個運算子的時候,就可以避免這些問題。
二、eval()
eval()只有一個參數。如果傳入的參數不是字串,它直接返回這個函數。如果參數是字串,它會把字串當成JavaScript代碼進行編譯,如果編譯失敗者拋出一個語法錯誤異常。如果編譯成功,則開始執行這一段代碼,並返回字串中的最後一個運算式會或語句的值,如果最後一個運算式或語句沒有值,則最終返回undefined。如果字串拋出一個異常,這個異常將把該調用傳遞給eval()。
關於eval最重要的是,它使用了調用它的變數範圍環境。也就是說,它尋找變數的值和定義新變數和函數的操作和局部範圍中的代碼完全一樣。如果一個函數定義了一個局部變數x,然後調用eval(“x”),它會返回局部變數的值。如果它調用eval(“x=1”),它會改變局部變數的值。如果函數調用了eval(“var y=2;”),它聲明了一個新的局部變數y,同樣地,一個函數可以通過如下代碼聲明一個局部變數:
eval(“function f(){return x+1;}”);
如果在最頂層的代碼中調用eval,當然,它會作用於全域變數和全域函數。
需要注意的是,傳遞給eval的字串必須在文法上將的通,不能通過eval往函數中任意粘貼程式碼片段,比如:eval(“return ;”)是沒有意義的,因為return只有在函數中才起到作用,並且事實上,eval的字串執行時的上下文環境和調用函數的上下文環境是一樣的,這不能使其作為函數的一部分來運行。如果字串作為一個單獨的指令碼是有語義的,那麼將其傳遞給eval作參數是完全沒有問題的,否則,eval會拋出語法錯誤異常。
三、全域eval()
eval()具有更改布局變數的能力,這對於JavaScript最佳化器來說是一個很大的問題。然而作為一種權宜之計,JavaScript解譯器針對那些調用了eval的函數所做的最佳化並不多。但當指令碼定義了eval的一個別名,且用另一個名稱調用它,JavaScript解譯器又會如何工作呢?為了讓JavaScript解譯器的實現更加簡化,ECMAScript3標準規定了任何解譯器都不允許對eval賦予別名。如果eval函數通過別名調用的話,則會拋出一個EavlError異常。
實際上,大多數的實現並不是這麼做的。當通過別名調用時,eval會將其字串當成頂層的全域代碼來執行。執行的代碼可能會定義新的全域變數和全域函數,或者給全域變數賦值,但卻不能使用或者修改主調函數中的局部變數,因此,這不會影響到函數內的代碼最佳化。
ECMAScript5是反對使用EavlError的,並且規範了eval的行為,“直接的eval”,當直接使用非限定的“eval”名稱來調用eval()函數時,通常稱為“直接eval”。直接調用eval()時,它總是在調用它的上下文範圍內執行。其他的間接調用則使用全域對象作為其上下文範圍,並且無法讀、寫、定義局部變數和函數。下面有一段範例程式碼:
複製代碼 代碼如下:
var geval=eval; //使用別名調用evla將是全域eval
var x="global",y="global"; //兩個全域變數
function f(){ //函數內執行的是局部eval
var x="local"; //定義局部變數
eval("x += ' chenged';");//直接使用eval改變的局部變數的值
return x; //返回更改後的局部變數
}
Function g(){ //這個函數內執行了全域eval
var y="local";
geval("y += ' changed';"); //直接調用改變了全域變數的值
return y;
}
console.log(f(),x); //改變了布局變了,輸出 “local changed global”
console.log(g(),y); //改變了全域變數,輸出 “local global changed”
全域的eval的這些行為不僅僅是處於代碼最佳化其的需要而作出的一種折中方案,它實際上是一種非常有用的特性,它允許我們執行那些對上下文沒有任何依賴的全域指令碼程式碼片段。真正需要eval來執行程式碼片段的情境並不多見。但當你真的意識到它的必要性的時候,你更可能會使用全域eval而不是局部eval。
四、嚴格eval()
ECMAScript5strict 模式對eval()函數的行為施加了更多的限制,甚至對標識符eval的使用也施加了限制。當在strict 模式下調用eval時,或者eval執行的程式碼片段以“Use strict” 指令開始,這裡的eval是私人上下文環境中的局部eval。也就是說,在strict 模式下,eval執行的程式碼片段可以查詢或更改局部變數,但不能在局部範圍中定義新的變數或函數。
此外,strict 模式將“eval”列為保留字,這讓eval()更像一個運算子。不能用一個別名覆蓋eval()函數。並且變數名,函數名。函數參數或者異常捕獲的參數都不能取名為eval。
寶劍鋒從磨礪出,梅花香自苦寒來。