還有壹種創建函數的方法。它很少被使用,但有些時候只能選擇它。
創建函數的語法:
let func = new Function ([arg1, arg2, ...argN], functionBody);
該函數是通過使用參數 arg1...argN
和給定的 functionBody
創建的。
下面這個例子可以幫助妳理解創建語法。這是壹個帶有兩個參數的函數:
let sum = new Function('a', 'b', 'return a + b'); alert( sum(1, 2) ); // 3
這裏有壹個沒有參數的函數,只有函數體:
let sayHi = new Function('alert("Hello")'); sayHi(); // Hello
與我們已知的其他方法相比,這種方法最大的不同在于,它實際上是通過運行時通過參數傳遞過來的字符串創建的。
以前的所有聲明方法都需要我們 —— 程序員,在腳本中編寫函數的代碼。
但是 new Function
允許我們將任意字符串變爲函數。例如,我們可以從服務器接收壹個新的函數並執行它:
let str = ... 動態地接收來自服務器的代碼 ... let func = new Function(str); func();
使用 new Function
創建函數的應用場景非常特殊,比如在複雜的 Web 應用程序中,我們需要從服務器獲取代碼或者動態地從模板編譯函數時才會使用。
通常,閉包是指使用壹個特殊的屬性 [[Environment]]
來記錄函數自身的創建時的環境的函數。它具體指向了函數創建時的詞法環境。(我們在 變量作用域,閉包 壹章中對此進行了詳細的講解)。
但是如果我們使用 new Function
創建壹個函數,那麽該函數的 [[Environment]]
並不指向當前的詞法環境,而是指向全局環境。
因此,此類函數無法訪問外部(outer)變量,只能訪問全局變量。
function getFunc() { let value = "test"; let func = new Function('alert(value)'); return func; } getFunc()(); // error: value is not defined
將其與常規行爲進行比較:
function getFunc() { let value = "test"; let func = function() { alert(value); }; return func; } getFunc()(); // "test",從 getFunc 的詞法環境中獲取的
new Function
的這種特性看起來有點奇怪,不過在實際中卻非常實用。
想象壹下我們必須通過壹個字符串來創建壹個函數。在編寫腳本時我們不會知道該函數的代碼(這也就是爲什麽我們不用常規方法創建函數),但在執行過程中會知道了。我們可能會從服務器或其他來源獲取它。
我們的新函數需要和主腳本進行交互。
如果這個函數能夠訪問外部(outer)變量會怎麽樣?
問題在于,在將 JavaScript 發布到生産環境之前,需要使用 壓縮程序(minifier) 對其進行壓縮 —— 壹個特殊的程序,通過刪除多余的注釋和空格等壓縮代碼 —— 更重要的是,將局部變量命名爲較短的變量。
例如,如果壹個函數有 let userName
,壓縮程序會把它替換爲 let a
(如果 a 已被占用了,那就使用其他字符),剩余的局部變量也會被進行類似的替換。壹般來說這樣的替換是安全的,畢竟這些變量是函數內的局部變量,函數外的任何東西都無法訪問它。在函數內部,壓縮程序會替換所有使用了這些變量的代碼。壓縮程序很聰明,它會分析代碼的結構,而不是呆板地查找然後替換,因此它不會“破壞”妳的程序。
但是在這種情況下,如果使 new Function
可以訪問自身函數以外的變量,它也很有可能無法找到重命名的 userName
,這是因爲新函數的創建發生在代碼壓縮以後,變量名已經被替換了。
即使我們可以在 new Function
中訪問外部詞法環境,我們也會受挫于壓縮程序。
此外,這樣的代碼在架構上很差並且容易出錯。
當我們需要向 new Function
創建出的新函數傳遞數據時,我們必須顯式地通過參數進行傳遞。
語法:
let func = new Function ([arg1, arg2, ...argN], functionBody);
由于曆史原因,參數也可以按逗號分隔符的形式給出。
以下三種聲明的含義相同:
new Function('a', 'b', 'return a + b'); // 基礎語法 new Function('a,b', 'return a + b'); // 逗號分隔 new Function('a , b', 'return a + b'); // 逗號和空格分隔
使用 new Function
創建的函數,它的 [[Environment]]
指向全局詞法環境,而不是函數所在的外部詞法環境。因此,我們不能在 new Function
中直接使用外部變量。不過這樣是好事,這有助于降低我們代碼出錯的可能。並且,從代碼架構上講,顯式地使用參數傳值是壹種更好的方法,並且避免了與使用壓縮程序而産生沖突的問題。