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()時,它總是在呼叫它的上下文作用域內執行。其他的間接呼叫則使用全域物件作為其上下文作用域,且無法讀取、寫入、定義局部變數和函數。下面有一段範例程式碼:
ECMAScript5嚴格模式對eval()函數的行為施加了更多的限制,甚至對標識符eval的使用也施加了限制。當在嚴格模式下呼叫eval時,或是eval執行的程式碼段以「Use strict」 指令開始,這裡的eval是私有上下文環境中的局部eval。也就是說,在嚴格模式下,eval執行的程式碼段可以查詢或更改局部變量,但不能在局部作用域中定義新的變數或函數。 此外,嚴格模式將「eval」列為保留字,這讓eval()更像一個運算符。不能用一個別名覆蓋eval()函數。且變數名,函數名。函數參數或異常捕獲的參數都不能取名為eval。
寶劍鋒從磨礪出,梅花香自苦寒來。