深入理解new Function

news2024/10/21 14:45:14

基础语法

let func = new Function([arg1,arg2,arg3,...argN],functionBody)

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

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

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

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: 一个没有参数的函数,自由函数体,没有参数的函数,在函数被调用的时候,等价于调用函数体里面的函数

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 后,传递的参数和函数体会被动态编译(下文会详细讲)为一个函数表达式,其编译后的代码组装方式如下

`function anonymous(${args.join(",")}
) {
${functionBody}
}`;

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

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

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)变量,只能访问全局变量。

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中

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中的代码执行时的作用域为当前作用域。它可以访问到函数中的局部变量。

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,可以将用户输入的字符串直接解析并执行。例如:

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 来生成一个能够动态计算表达式的函数。例如:

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 有时用于生成查询逻辑的动态函数,以便在大规模数据上进行快速计算和过滤。

/ 用户输入的查询条件
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 可以根据用户定义的规则生成函数,实时验证表单字段或计算结果:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java项目-基于springcloud框架的分布式架构网上商城系统项目实战(附源码+文档)

作者:计算机学长阿伟 开发技术:SpringBoot、SSM、Vue、MySQL、ElementUI等,“文末源码”。 开发运行环境 开发语言:Java数据库:MySQL技术:SpringBoot、Vue、Mybaits Plus、ELementUI工具:IDEA/…

背包九讲——完全背包问题

目录 完全背包问题 问题定义 动态规划解法 状态转移方程 初始化 遍历顺序 三种解法: 朴素版——枚举k 进阶版——dp正推(一维滚动数组) 背包问题第三讲——完全背包问题 背包问题是一类经典的组合优化问题,通常涉及在限定…

PCB走线线径与电流关系

转载自一个实验搞明白PCB走线应该画多宽_哔哩哔哩_bilibili

2011年国赛高教杯数学建模A题城市表层土壤重金属污染分析解题全过程文档及程序

2011年国赛高教杯数学建模 A题 城市表层土壤重金属污染分析 随着城市经济的快速发展和城市人口的不断增加,人类活动对城市环境质量的影响日显突出。对城市土壤地质环境异常的查证,以及如何应用查证获得的海量数据资料开展城市环境质量评价,研…

什么是智能电网?

智能电网(Smart Grid)被认为是当今电力行业发展的重要方向之一。它是传统电网与现代信息技术、通信技术和自动化技术深度融合的产物,旨在提高电力系统的效率、可靠性和可持续性。智能电网不仅仅是一个技术创新的名词,更是一个系统…

全域推广什么意思?如何搭建高效优质的全域推广服务商系统?

当前,全域推广一词的热度日渐升高,越来越多的人开始关注和计划入局这一全新项目,希望能够吃到第一波红利。不过,由于这一项目刚刚兴起,相关资料尚不完善,因此,绝大多数有意向入局的人都对该项目…

创客项目秀 | 基于使用 XIAO BLE Sense 和 Edge Impulse 的宠物活动跟踪器

今天为大家带来的是来自美国的创作者米顿-达斯的作品:宠物活动跟踪器.这个装置主要是为宠物主人提供关于宠物日常活动量的详尽数据,还能够根据宠物的独特需求,提供个性化的健康建议和活动指导。 项目背景 为了全面促进宠物的健康与活力,采用…

来可电子CAN数据记录仪通过智诊小助手TF卡记录文件导出

若想将TF卡中记录的数据文件导出可按以下的流程进行配置: 点击主界面中的导出选项即可进入到下图中TF卡应用界面 点击TF卡应用界面中“查看记录文件”的选项,进入导出文件界面。 点击“选择”进入勾选文件的界面 点击“导出”后,点击“确定”…

Vulnhub打靶-napping

基本信息 靶机下载:https://download.vulnhub.com/napping/napping-1.0.1.ova 攻击机器:192.168.20.128(Windows操作系统)& 192.168.20.138(kali) 提示信息:甚至管理员也可以在工作中睡…

统信UOS与Windows11传输数据

原文连接:统信UOS与Windows11相互传输数据 hello,大家好啊,今天给大家带来一篇统信UOS与Windows11之间通过共享文件夹传输数据的方法,首先在Windows11上创建共享文件夹,然后通过smb协议在UOS上进行连接访问&#xff0c…

彻底解决IDEA SpringBoot项目yml文件没有小树叶,读取配置文件失败问题

报错说没有配置dubbo:application:name,其实是配置了的,就是读不到,那有没有可能是yml文件不是绿叶的问题?网上查了很多文章配置小绿叶,最后还是报这个错,而且网上的文章配置小绿叶也太过于繁琐,其实就一招…

【Java后端】之 ThreadLocal 详解

想象一下,你有一个工具箱,里面放着各种工具。在多人共用这个工具箱的时候,很容易出现混乱,比如有人拿走了你的锤子,或者你找不到合适的螺丝刀。为了避免这种情况,最好的办法就是每个人都有自己独立的工具箱…

Wasm解析入口分析

直接postman请求,发现返回了一个wasm,需要解析这个wasm获取到参数。下面找到页面入口,如下: 也是个VM解析,VM太多了,扣出来就行,不难,就是麻烦的很。

6个最佳核心应用仪表盘构建工具

核心应用仪表盘(Core App Dashboard)的概念或许你不太熟悉,但仪表盘你一定不陌生。 从汽车的仪表盘显示速度和油量,到运动手环仪表盘追踪步数和心率,再到金融投资仪表盘监控股票和基金的实时行情,它们通过…

Flink窗口分配器WindowAssigner

前言 Flink 数据流经过 keyBy 分组后,下一步就是 WindowAssigner。 WindowAssigner 定义了 stream 中的元素如何被分发到各个窗口,元素可以被分发到一个或多个窗口中,Flink 内置了常用的窗口分配器,包括:tumbling wi…

JAVA线程的多种状态

线程的状态图 new状态(新建状态): 创建了一个线程的对象,但是这个线程没有启动start,那么此时这个线程的状态就是NEW也就是新建状态 此时线程对象就是一个普通的JAVA对象,CPU还没有给其分配资源 public class Main16 {…

微信小程序案例:计算器(含代码)

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

动态中的守候:滑动窗口与距离的诗篇

公主请阅 1. 长度最小的子数组1.1 题目说明 示例 1 示例 1 示例 2 示例 3 1.2 题目分析1.3 代码部分1.4 代码分析 2. 无重复字符的最长子串2.1 题目说明示例 1示例 1示例 2示例 3 2.2 题目分析2.3 代码部分2.4 代码分析2.5 代码深度分析 1. 长度最小的子数组 题目传送门 1.1 题…

2020年计算机网络408真题解析

第一题: 解析:OSI参考模型网络协议的三要素 网络协议的三要素:语法 ,语义,同步(时序) 语法:定义收发双方所交换信息的格式 语法:定义收发双方所要完成的操作 网页的加载 …

「iOS」——YYModel学习

iOS学习 前言优势使用方法简单的Model与JSON互转多样化的数据类型交换容器类数据交换 model中包含其他model白名单与黑名单 总结 前言 YYModel是YYKit的高效组件之一,在实际场景中的非常实用,在项目中使用MVC架构时,可以简化数据处理。在性能…