深入理解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
相关推荐
梦想科研社2 分钟前
【无人机设计与控制】红嘴蓝鹊优化器RBMO求解无人机路径规划MATLAB
开发语言·matlab·无人机
混迹网络的权某5 分钟前
每天一道C语言精选编程题之求数字的每⼀位之和
c语言·开发语言·考研·算法·改行学it·1024程序员节
一只特立独行的猪6111 小时前
Java面试题——微服务篇
java·开发语言·微服务
码农幻想梦3 小时前
实验九 视图的使用
前端·数据库·oracle
喵手3 小时前
Java 与 Oracle 数据泵实操:数据导入导出的全方位指南
java·开发语言·oracle
硬汉嵌入式4 小时前
H7-TOOL的LUA小程序教程第16期:脉冲测量,4路PWM,多路GPIO和波形打印(2024-10-25, 更新完毕)
开发语言·junit·小程序·lua
Wx120不知道取啥名4 小时前
C语言之长整型有符号数与短整型有符号数转换
c语言·开发语言·单片机·mcu·算法·1024程序员节
开心工作室_kaic5 小时前
ssm010基于ssm的新能源汽车在线租赁管理系统(论文+源码)_kaic
java·前端·spring boot·后端·汽车
Python私教5 小时前
Flutter颜色和主题
开发语言·javascript·flutter
代码吐槽菌5 小时前
基于SSM的汽车客运站管理系统【附源码】
java·开发语言·数据库·spring boot·后端·汽车