-
定义:new Function都可以将一段字符串解析成一段JS脚本并执行
-
语法:
new Function(functionBody)ornew Function(arg1, arg2, /* …, */ argN, functionBody) -
注意:作用域问题,作用域为全局,不能访问当前上下文 【the Function constructor creates functions which execute in the global scope only】
let foo = "foo"; function bar() { let foo = "bar"; eval("console.log(foo)"); // 输出bar new Function("console.log(foo)")(); // 输出foo,当前上下文foo = "bar"访问不到 } bar(); -
用法梳理:
- 1、我们使用
new Function('return window')()或者Function('return window')()返回全局的 widow 上下文对下,这在微前端中有一定的使用场景,通过这种方法实现在子应用破局获取到全局顶层的上下文 - 2、我们同样可以使用
new Function('return (params) => params.a')()或者new Function('return function exc(params){return params.a}')()返回一个函数,去动态执行 - 3、new Function 拼接字符串: new Function(
return+ custom)【注意,return 后面有个空格】
// 1、例一 const x = new Function(`params`, `return params?.a`); x({ a: 222 }); // 222 // 2、例二 const params = { a: 222 }; const x = new Function(`return ` + params?.a); x(); // 222 // 3、例三 var name = (params) => { return params?.a; }; const x = new Function(`return` + name); x()({ a: 222 }); // 222- 几种传参方式:
// 1、传统的入参方式单个元素: var sum = new Function("a", "b", "return a + b"); console.log(sum(2, 6)); // 2、传统的入参方式对象: var sum = new Function("params", "return params.a + params.b"); console.log(sum({ a: 2, b: 6 })); // 3、将顶层变量直接定义好传入: var sum = new Function("var a=100; return a + 100"); console.log(sum()); // 4、使用call的方式调起 // @1: var sum = new Function('return this.a + 100').call({a:10}) console.log(sum); // @2: const findNum = new Function( "function findLargestNumber (arr) { return Math.max(...arr) }; return findLargestNumber", ); console.log(findNum.call({}).call({}, [2, 4, 1, 8, 5])); - 1、我们使用
-
需要注意的事:
- 1、我们尽量使用显示传递参数【代码在执行压缩的过程中,全局的变量名会被替换,而 new Function 中的函数体可能是字符串,不会被同步替换,这样压缩后就有出错的风险】, 这时我们需要使用
显式传递数据方式来解决
// 1、源码 const userName = "Tom"; const func = new Function("return userName"); // 2、被压缩后 const a = "Tom"; const func = new Function("return userName"); //这个地方没被替换 // 3、分析:上面的代码,在压缩条件下,执行时就可能报错,userName没被替换,全局已经不存在了 // 4、如何破解 // 显式传递数据: 因为 new Function 无法访问外部变量,并且会受到压缩影响,推荐的做法是显式通过函数参数传递需要使用的数据,而不是依赖外部变量 // 所以减少直接使用,采用显式传递数据更安全更合理 const userName = "Tom"; const func = new Function("name", "return name"); console.log(func(userName)); //这样可以正常工作 - 1、我们尽量使用显示传递参数【代码在执行压缩的过程中,全局的变量名会被替换,而 new Function 中的函数体可能是字符串,不会被同步替换,这样压缩后就有出错的风险】, 这时我们需要使用
-
使用场景:
-
1、低代码和泛低代码工程
- 低代码本质上,是将用户配置的字符串配置,给予转换执行,最终渲染出组件;new Function 在执行字符串方面有天然无与伦比的能力,那么可想而知,new Function 在低代码领域必然有很强的使用性;当然对于需要执行字符串的泛应用场景肯定也是使用满满。下面截取几个知名的低代码工程中的
new Function使用:
/** * lowcode-engine use demo **/ function checkPropTypes( value: any, name: string, rule: any, componentName: string ): boolean { let ruleFunction = rule; if (typeof rule === "object") { // eslint-disable-next-line no-new-func ruleFunction = new Function( `"use strict"; const PropTypes = arguments[0]; return ${transformPropTypesRuleToString( rule )}` )(PropTypes2); } if (typeof rule === "string") { // eslint-disable-next-line no-new-func ruleFunction = new Function( `"use strict"; const PropTypes = arguments[0]; return ${transformPropTypesRuleToString( rule )}` )(PropTypes2); } if (!ruleFunction || typeof ruleFunction !== "function") { logger.warn("checkPropTypes should have a function type rule argument"); return true; } const err = ruleFunction( { [name]: value, }, name, componentName, "prop", null, ReactPropTypesSecret ); if (err) { logger.warn(err); } return !err; }/** * appsmith use demo **/ function parseConfigurationForCallbackFns( chartConfig: Record<string, unknown>, _: any, ) { const config: Record<string, unknown> = _.cloneDeep(chartConfig); const fnKeys = (config["__fn_keys__"] as string[]) ?? []; for (let i = 0; i < fnKeys.length; i++) { const fnString = _.get(config, fnKeys[i]); const fn = new Function("return " + fnString)(); _.set(config, fnKeys[i], fn); } return config; } - 低代码本质上,是将用户配置的字符串配置,给予转换执行,最终渲染出组件;new Function 在执行字符串方面有天然无与伦比的能力,那么可想而知,new Function 在低代码领域必然有很强的使用性;当然对于需要执行字符串的泛应用场景肯定也是使用满满。下面截取几个知名的低代码工程中的
-
微前端沙箱环境
- 使用new Function创建沙箱主要是为了隔离变量,防止全局污染
// 基础型,单new Function型的 // 创建一个沙箱 function createSandbox() { var sandbox = { run: new Function('print', 'code', 'return eval(code)') }; sandbox.run(sandbox.print = text => console.log(text), 'print("Hello, Sandbox!")'); return sandbox; } // 使用沙箱 var sandbox = createSandbox();// 配合with升级沙箱作用域限定 const ctx = { test(flag){ console.log(flag); } }; function sandbox(code) { const fnbody = "with (ctx) {" + code + "}"; return new Function("ctx", fnbody); } const code = ` const name = 'zhangsan'; test(name) `; sandbox(code)(ctx); // 限定请求作用域为ctx,全局变量有被篡改的风险// 严格模式的沙箱实现: with + new Function + proxy实现 // 核心思路是通过 with 块和 Proxy 对象来隔离执行环境,确保执行的代码只能访问到沙盒内的变量。任何在沙盒内声明或者修改的变量都不会影响到全局作用域,同时,全局作用域下的变量在沙盒内也是不可见的 // 创建一个沙盒对象,这个对象里面的属性和全局作用域不同步,避免沙盒内代码影响外部环境 const sandboxProxy = new Proxy({}, { has: function() { // 拦截属性检查,总是返回 false,迫使 with 块中的查找进入沙盒对象 return true; }, get: function(target, key) { if (key === Symbol.unscopables) return undefined; // 返回沙盒对象中的属性,如果不存在则返回 undefined return target[key]; }, set: function(target, key, value) { // 设置属性值,影响只限于沙盒内部 target[key] = value; return true; } }); // 定义执行沙盒代码的函数 function executeSandboxCode(code) { /* // 通过 new Function 创建一个新的函数,这样保证了函数体内的代码运行在全局作用域之外 const sandboxFunction = new Function('sandbox', `with(sandbox) { ${code} }`); // 调用这个新创建的函数,传入沙盒代理对象 sandboxFunction(sandboxProxy); */ // 避免绕过沙盒,通过改变 this 指向的代码示例 const sandboxFunction = new Function('sandbox', 'with(sandbox) { return function() { "use strict"; ' + code + ' } }'); sandboxFunction(sandboxProxy).call(null); } // 使用 executeSandboxCode(` // 这些代码运行在沙盒环境中,外部变量对其不可见 var secret = '我是沙盒中的秘密'; console.log(secret); // 输出: '我是沙盒中的秘密' `);/** * lowcode-engine use demo **/ function parseExpression(a: any, b?: any, c = false) { let str; let self; let thisRequired; let logScope; if (typeof a === 'object' && b === undefined) { str = a.str; self = a.self; thisRequired = a.thisRequired; logScope = a.logScope; } else { str = a; self = b; thisRequired = c; } try { const contextArr = ['"use strict";', 'var __self = arguments[0];']; contextArr.push('return '); let tarStr: string; tarStr = (str.value || '').trim(); // NOTE: use __self replace 'this' in the original function str // may be wrong in extreme case which contains '__self' already tarStr = tarStr.replace(/this(\W|$)/g, (_a: any, b: any) => `__self${b}`); tarStr = contextArr.join('\n') + tarStr; // 默认调用顶层窗口的parseObj, 保障new Function的window对象是顶层的window对象 if (inSameDomain() && (window.parent as any).__newFunc) { return (window.parent as any).__newFunc(tarStr)(self); } const code = `with(${thisRequired ? '{}' : '$scope || {}'}) { ${tarStr} }`; return new Function('$scope', code)(self); } catch (err) { logger.error(`${logScope || ''} parseExpression.error`, err, str, self?.__self ?? self); return undefined; } }
-
-
参考:
- 官网:developer.mozilla.org/en-US/docs/…
- 作用域问题 【 tsejx.github.io/javascript-… 】
- 深度一点的博文 【 juejin.cn/post/741784… 】
- 和 eval 的区别 【 segmentfault.com/a/119000002… 】
- 沙箱实现:zhuanlan.zhihu.com/p/702058045