深入理解new Function

基础语法

javascript 复制代码
let func = new Function([arg1,arg2,arg3,...argN],functionBody)

函数是通过使用参数 arg1...argN 和给定的 functionBody 创建。

调用 Function 时可以使用或不使用 new,两者都会创建一个新的 Function 实例

举例1: 带有两个参数的函数

javascript 复制代码
let sum = new Function('a', 'b', 'return a + b');
console.log(sum(1, 2)); // 3

let sum1 = new Function('a', ' b', 'return a + b');
console.log(sum1(1, 2)); // 3

let sum2 = new Function('a,b', 'return a + b');
console.log(sum2(1, 2)); // 3

let sum3 = new Function('a , b', 'return a + b');
console.log(sum3(1, 2)); // 3

let sum4 = new Function(['a,b'], 'return a + b');
console.log(sum4(1, 2)); // 3

Function函数最后一个参数永远都是函数体,由于历史原因函数的参数部分支持以逗号分割的形式展示

举例2: 一个没有参数的函数,自由函数体,没有参数的函数,在函数被调用的时候,等价于调用函数体里面的函数

javascript 复制代码
const sumOfArray = new Function(
  "const sumArray = (arr) => arr.reduce((previousValue, currentValue) => previousValue + currentValue); return sumArray",
)();

// 调用函数 调用的是sumOfArray函数 但实际执行的是sumArray函数
console.log(sumOfArray([1, 2, 3, 4]))
// 10

new Function 没有参数的时候,实际上返回的是一个匿名函数(就是你传入的函数体)

解析过程

new Function的解析过程是先解析参数部分再解析函数体部分,如果参数部分有异常就会直接抛出异常,函数体就不会被执行,只有参数部分没有异常,才会解析函数体部分。这种解析方式确保了逐步验证的过程,可以防止通过传递非法参数或函数体的方式进行代码注入攻击。比如,不允许通过参数注入注释符号 /* 来破坏整个函数的结构。也就在一定程度上防止代码注入的逻辑

匿名函数

使用 new Function 后,传递的参数和函数体会被动态编译(下文会详细讲)为一个函数表达式,其编译后的代码组装方式如下

javascript 复制代码
`function anonymous(${args.join(",")}
) {
${functionBody}
}`;

与普通的函数表达式不同,anonymous 这个名字不会被添加到 functionBody 的作用域中,因为 functionBody 只能访问全局作用域

anonymous 的这种编译后的组装格式,可以通过 toString() 函数查看。举个例子

javascript 复制代码
const queryCondition = "return obj.age > 30 && obj.country === 'USA';"; 
const queryFunc = new Function('obj', queryCondition);
console.log(queryFunc.toString());
// function anonymous(obj
) {
return obj.age > 30 && obj.country === 'USA';
}

作用域

闭包是指使用一个特殊的属性 [[Environment]] 来记录函数自身的创建时的环境的函数。它具体指向了函数创建时的词法环境。

但是 new Function 创建一个函数,那么该函数的 [[Environment]] 并不指向当前的词法环境,而是指向全局环境,Function 构造函数创建的函数仅在全局作用域中执行。因此,此类函数无法访问外部(outer)变量,只能访问全局变量。

javascript 复制代码
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 中,顶级作用域不是全局作用域

严格模式下或Node中

javascript 复制代码
let value2 = "芝士outer";
function getFunc() {
  let value = "芝士inter";
  let func = new Function('alert(value)');
  return func;
}
function getFunc2() {
  let func = new Function('alert(value2)');
  return func;
}

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

new Function和eval的区别

与Function只能访问全局作用域不同的是,eval中的代码执行时的作用域为当前作用域。它可以访问到函数中的局部变量。

javascript 复制代码
let a = 1
let fn = function () {
  let a = 2
  console.log(eval('a+8')); //10
  let result2 = function () {
    console.log(a)
  }
  result2() //打印出2
}
fn()

eval是一个危险的函数,它使用与调用者相同的权限执行代码。如果你用eval运行的字符串被 不怀好意的进行恶意修改,可能会造成全局污染或者一些不同方式的代码攻击。

eval函数通常比其他函数方法解析的更慢,因为它必须调用js解析器进行解析,而其他的函数可能被现代js引擎直接优化。

应用场景

new Function 应用场景其实还蛮多的,主要涉及的方面是动态代码执行、沙箱环境以及性能优化等。

1、动态执行代码

在一些低代码或无代码平台中,用户可能会输入自定义的 JavaScript 代码,平台需要即时执行这些代码。通过 new Function,可以将用户输入的字符串直接解析并执行。例如:

javascript 复制代码
const userInput = "return a + b;";
const dynamicFunction = new Function('a', 'b', userInput);
console.log(dynamicFunction(2, 3)); // 输出 5

这种场景常见于表单计算器、在线编程环境或数据分析工具中,允许用户定义自定义的计算公式或规则。

2、模版引擎中的表达式解析

在一些模板引擎(如 Vue.js 的 v-bind、Angular 的模板表达式)中,开发者可能会通过字符串表达式绑定数据或属性。为了提高执行效率和灵活性,框架有时会通过 new Function 来生成一个能够动态计算表达式的函数。例如:

javascript 复制代码
const expression = "data.a + data.b";
const compute = new Function('data', `return ${expression}`);
console.log(compute({a: 1, b: 2})); // 输出 3

通过 new Function 动态编译模板表达式,可以避免每次都对表达式重新进行解析和计算,提高性能。

3、JSONPath或XPath解析器

在数据查询场景中,像 JSONPath 或 XPath这种解析器,用户的查询条件通常是通过字符串输入的。 解析器需要根据用户输入的查询条件动态执行查询操作。这类解析器中,new Function 有时用于生成查询逻辑的动态函数,以便在大规模数据上进行快速计算和过滤。

javascript 复制代码
/ 用户输入的查询条件
const queryCondition = "return obj.age > 30 && obj.country === 'USA';";
// 动态生成一个查询函数
const queryFunc = new Function('obj', queryCondition);

// 用于查询的JSON数据
const data = [
  { name: 'Alice', age: 35, country: 'USA' },
  { name: 'Bob', age: 28, country: 'UK' },
  { name: 'Charlie', age: 40, country: 'USA' }
];

// 动态过滤数据
const result = data.filter(queryFunc);
console.log(result); // [{ name: 'Alice', age: 35, country: 'USA' }, { name: 'Charlie', age: 40, country: 'USA' }]

4、性能优化避免eval

new Function 与 eval 类似,可以动态执行字符串代码,但相比于 eval,new Function 的作用域更为严格。它只允许访问全局作用域,不能直接访问当前上下文的局部变量。因此,new Function 在某些需要动态执行代码的场景下比 eval 更安全,也更快,常用于替代 eval

5、表单验证或计算

在复杂的动态表单中,可能需要根据用户的输入动态生成验证规则或者计算结果。new Function 可以根据用户定义的规则生成函数,实时验证表单字段或计算结果:

javascript 复制代码
const rule = "return value > 10;";
const validate = new Function('value', rule);
console.log(validate(15)); // 输出 true

6、JavaScript沙箱实现

某些框架需要隔离用户输入的代码与主应用逻辑,创建安全的沙箱环境。通过 new Function,可以限制代码的作用域,防止访问不安全的全局变量。这种方式在在线 IDE 或某些插件系统中较为常见。

new Function 不太合适的场景和弊端注意点*

在将 JavaScript 发布到生产环境之前,需要使用 压缩程序(minifier) 对其进行压缩。

压缩程序(minifier)工作原理:

  • 压缩程序用于优化 JavaScript 代码,将变量名压缩成更短的名称,并删除注释、空格等无关内容。这样做能减少代码体积,提升性能。
  • 压缩时,局部变量(如 let userName)会被替换为较短的变量名(如 let a),所有引用该变量的地方也会同步替换。

new Function 访问外部变量的限制

  • 使用 new Function 创建的函数不会像普通函数那样能够访问外部的词法作用域(即闭包环境)。它只能访问传递给它的参数和全局变量
  • 在 new Function 中,即使我们尝试引用外部定义的变量(如 userName),由于新函数的词法作用域与外部环境隔离,它是无法直接访问这些变量的

压缩程序的影响:

  • 假设代码在开发时有一个变量 let userName,而你在 new Function 中尝试访问它。压缩后,userName 可能会变成 a。但 new Function 在代码压缩后才被执行,它不会知道压缩后变量名的变化,因此会导致访问出错
  • 例如,如果你的代码中有:
  • let userName = "John"; const func = new Function("return userName;");
    压缩后,userName 可能被替换为 a,但是 new Function 生成的函数依然试图访问 userName,结果会报错,因为压缩程序不会知道 new Function 中定义的内容。

正确方式:显式传递数据:

  • 因为 new Function 无法访问外部变量,并且会受到压缩影响,推荐的做法是显式通过函数参数传递需要使用的数据,而不是依赖外部变量
  • 例如:
  • let userName = "John"; const func = new Function('name', 'return name;'); console.log(func(userName)); // 这样可以正常工作 返回John
相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试