一般我们不会使用用 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()。