一般我們不會使用用 new Function 構造函數的,因為沒必要,直接使用 function 或者 箭頭函數寫法更簡單。但并不是說new Function 構造函數無用。在一些特別的場景,比如函數體的數據格式是字符串的時候,new Function 構造函數的作用就顯示出來了。之前也是僅僅知道此方法,但是沒有具體的研究搞懂,但是最近一兩年一直在倒騰低代碼的項目,原理上來說,低代碼都是一堆字符串,為了解析字符串就使用了new Function 構造函數(eval方法也是可以的),在此在記錄一下,加深理解。詳情參見:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions
1、語法
備注: 不推薦使用 Function 構造函數創建函數,因為它需要的函數體作為字符串可能會阻止一些 JS 引擎優化,也會引起其他問題。把 Function 的構造函數當作函數一樣調用 (不使用 new 操作符) 的效果與作為 Function 的構造函數調用一樣。
let func = new Function ([arg1, arg2, ...argN], functionBody);
//等價于 let func = Function ([arg1, arg2, ...argN], functionBody);
<font color="red">Function構造函數所有的參數都是字符串類型。除了最后一個參數, 其余的參數都作為生成函數的參數即形參。這里可以沒有參數。最后一個參數, 表示的是要創建函數的函數體。</font>
//傳入參數
let sum = new Function('a', 'b', 'return a + b');
console.log( sum(1, 2) ); // 3
//不傳入參數
let sum = new Function('console.log(1)');
console.log( sum() ); // 1
由于歷史原因,new Function參數也可以以逗號分隔的列表形式給出。下邊這三個聲明的含義相同:
new Function('a', 'b', 'return a + b');
new Function('a,b', 'return a + b');
new Function('a , b', 'return a + b');
2、作用域
Function()構造函數和函數有一點就是:使用構造函數Function()創建的函數不使用當前的詞法作用域,相反的,它們總是被頂級函數來編譯,因此在運行時它們只能訪問全局變量和自己的局部變量.
let a = 1
let fn = function(){
let a = 2
let result1 = new Function('console.log(a)')
let result2 = function(){
console.log(a)
}
result1() //打印出1,訪問的是全局變量a
result2() //打印出2
}
fn()
// new Function這樣的函數不能訪問外部變量,只能訪問全局變量
// 雖然這段代碼可以在瀏覽器中正常運行,但在 Node.js 中,result1() 執行會報錯,因為找不到變量 a。
// 這是因為,在 Node 中,頂級作用域不是全局作用域,而 a 其實是在模塊的作用域之中。
想象一下,我們必須從一個字符串創建一個函數。該函數的代碼在編寫腳本時是未知的,但是會在執行過程中知道。我們可能會從服務器或其他地方接收到它。此時我們的新函數需要與主腳本交互,如果它此時它可以訪問外部變量,那么就可以操作外部變量,改變外部變量,這樣就會引發不可預估的風險。
3、使用
假如有一個非合法 JSON 對象字符串,如下:
let str = "{ name: '小坦克', code: 100 }"
JSON.parse(str) // 會報錯,因為str字段是不符合規范的對象字符串(key,value都必須是"",雙引號包裹)
可以使用new Function
let str = "{ name: '小坦克', code: 100 }"
let result = JSON.parse(new Function('return ' + str)()) // result = { name: '小坦克', code: 100 }
4、new Function和eval的區別
eval中的代碼執行時的作用域為當前作用域。它可以訪問到函數中的局部變量。
let a = 1
let fn = function(){
let a = 2
let result1 = new Function('console.log(a)')
let result2 = eval('console.log(a)') //打印出2
result1() //打印出1,訪問的是全局變量a
}
fn()
永遠不要使用 eval !!!
eval() 是一個危險的函數, 它使用與調用者相同的權限執行代碼。如果你用 eval() 運行的字符串代碼被惡意方(不懷好意的人)修改,您最終可能會在您的網頁/擴展程序的權限下,在用戶計算機上運行惡意代碼。更重要的是,第三方代碼可以看到某一個 eval() 被調用時的作用域,這也有可能導致一些不同方式的攻擊。相似的 Function 就不容易被攻擊
eval() 通常比其他替代方法更慢,因為它必須調用 JS 解釋器,而許多其他結構則可被現代 JS 引擎進行優化。
此外,現代 JavaScript 解釋器將 JavaScript 轉換為機器代碼。這意味著任何變量命名的概念都會被刪除。因此,任意一個 eval 的使用都會強制瀏覽器進行冗長的變量名稱查找,以確定變量在機器代碼中的位置并設置其值。另外,新內容將會通過 eval() 引進給變量,比如更改該變量的類型,因此會強制瀏覽器重新執行所有已經生成的機器代碼以進行補償。但是(謝天謝地)存在一個非常好的 eval 替代方法:只需使用 window.Function。這有個例子方便你了解如何將eval()的使用轉變為Function()。