基础语法
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