JS

深入理解Js里new Function语法

2022-09-07  本文已影响0人  _littleTank_

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

上一篇下一篇

猜你喜欢

热点阅读