Javascript "new Function" 語(yǔ)法

2023-02-17 10:50 更新

還有一種創(chuàng)建函數(shù)的方法。它很少被使用,但有些時(shí)候只能選擇它。

語(yǔ)法

創(chuàng)建函數(shù)的語(yǔ)法:

let func = new Function ([arg1, arg2, ...argN], functionBody);

該函數(shù)是通過(guò)使用參數(shù) arg1...argN 和給定的 functionBody 創(chuàng)建的。

下面這個(gè)例子可以幫助你理解創(chuàng)建語(yǔ)法。這是一個(gè)帶有兩個(gè)參數(shù)的函數(shù):

let sum = new Function('a', 'b', 'return a + b');

alert( sum(1, 2) ); // 3

這里有一個(gè)沒(méi)有參數(shù)的函數(shù),只有函數(shù)體:

let sayHi = new Function('alert("Hello")');

sayHi(); // Hello

與我們已知的其他方法相比,這種方法最大的不同在于,它實(shí)際上是通過(guò)運(yùn)行時(shí)通過(guò)參數(shù)傳遞過(guò)來(lái)的字符串創(chuàng)建的。

以前的所有聲明方法都需要我們 —— 程序員,在腳本中編寫函數(shù)的代碼。

但是 new Function 允許我們將任意字符串變?yōu)楹瘮?shù)。例如,我們可以從服務(wù)器接收一個(gè)新的函數(shù)并執(zhí)行它:

let str = ... 動(dòng)態(tài)地接收來(lái)自服務(wù)器的代碼 ...

let func = new Function(str);
func();

使用 new Function 創(chuàng)建函數(shù)的應(yīng)用場(chǎng)景非常特殊,比如在復(fù)雜的 Web 應(yīng)用程序中,我們需要從服務(wù)器獲取代碼或者動(dòng)態(tài)地從模板編譯函數(shù)時(shí)才會(huì)使用。

閉包

通常,閉包是指使用一個(gè)特殊的屬性 [[Environment]] 來(lái)記錄函數(shù)自身的創(chuàng)建時(shí)的環(huán)境的函數(shù)。它具體指向了函數(shù)創(chuàng)建時(shí)的詞法環(huán)境。(我們?cè)?nbsp;變量作用域,閉包 一章中對(duì)此進(jìn)行了詳細(xì)的講解)。

但是如果我們使用 new Function 創(chuàng)建一個(gè)函數(shù),那么該函數(shù)的 [[Environment]] 并不指向當(dāng)前的詞法環(huán)境,而是指向全局環(huán)境。

因此,此類函數(shù)無(wú)法訪問(wèn)外部(outer)變量,只能訪問(wèn)全局變量。

function getFunc() {
  let value = "test";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // error: value is not defined

將其與常規(guī)行為進(jìn)行比較:

function getFunc() {
  let value = "test";

  let func = function() { alert(value); };

  return func;
}

getFunc()(); // "test",從 getFunc 的詞法環(huán)境中獲取的

new Function 的這種特性看起來(lái)有點(diǎn)奇怪,不過(guò)在實(shí)際中卻非常實(shí)用。

想象一下我們必須通過(guò)一個(gè)字符串來(lái)創(chuàng)建一個(gè)函數(shù)。在編寫腳本時(shí)我們不會(huì)知道該函數(shù)的代碼(這也就是為什么我們不用常規(guī)方法創(chuàng)建函數(shù)),但在執(zhí)行過(guò)程中會(huì)知道了。我們可能會(huì)從服務(wù)器或其他來(lái)源獲取它。

我們的新函數(shù)需要和主腳本進(jìn)行交互。

如果這個(gè)函數(shù)能夠訪問(wèn)外部(outer)變量會(huì)怎么樣?

問(wèn)題在于,在將 JavaScript 發(fā)布到生產(chǎn)環(huán)境之前,需要使用 壓縮程序(minifier) 對(duì)其進(jìn)行壓縮 —— 一個(gè)特殊的程序,通過(guò)刪除多余的注釋和空格等壓縮代碼 —— 更重要的是,將局部變量命名為較短的變量。

例如,如果一個(gè)函數(shù)有 let userName,壓縮程序會(huì)把它替換為 let a(如果 a 已被占用了,那就使用其他字符),剩余的局部變量也會(huì)被進(jìn)行類似的替換。一般來(lái)說(shuō)這樣的替換是安全的,畢竟這些變量是函數(shù)內(nèi)的局部變量,函數(shù)外的任何東西都無(wú)法訪問(wèn)它。在函數(shù)內(nèi)部,壓縮程序會(huì)替換所有使用了使用了這些變量的代碼。壓縮程序很聰明,它會(huì)分析代碼的結(jié)構(gòu),而不是呆板地查找然后替換,因此它不會(huì)“破壞”你的程序。

但是在這種情況下,如果使 new Function 可以訪問(wèn)自身函數(shù)以外的變量,它也很有可能無(wú)法找到重命名的 userName,這是因?yàn)樾潞瘮?shù)的創(chuàng)建發(fā)生在代碼壓縮以后,變量名已經(jīng)被替換了。

即使我們可以在 new Function 中訪問(wèn)外部詞法環(huán)境,我們也會(huì)受挫于壓縮程序。

此外,這樣的代碼在架構(gòu)上很差并且容易出錯(cuò)。

當(dāng)我們需要向 ?new Function? 創(chuàng)建出的新函數(shù)傳遞數(shù)據(jù)時(shí),我們必須顯式地通過(guò)參數(shù)進(jìn)行傳遞。

總結(jié)

語(yǔ)法:

let func = new Function ([arg1, arg2, ...argN], functionBody);

由于歷史原因,參數(shù)也可以按逗號(hào)分隔符的形式給出。

以下三種聲明的含義相同:

new Function('a', 'b', 'return a + b'); // 基礎(chǔ)語(yǔ)法
new Function('a,b', 'return a + b'); // 逗號(hào)分隔
new Function('a , b', 'return a + b'); // 逗號(hào)和空格分隔

使用 new Function 創(chuàng)建的函數(shù),它的 [[Environment]] 指向全局詞法環(huán)境,而不是函數(shù)所在的外部詞法環(huán)境。因此,我們不能在 new Function 中直接使用外部變量。不過(guò)這樣是好事,這有助于降低我們代碼出錯(cuò)的可能。并且,從代碼架構(gòu)上講,顯式地使用參數(shù)傳值是一種更好的方法,并且避免了與使用壓縮程序而產(chǎn)生沖突的問(wèn)題。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)