前端小伙伴们,有没有被动态执行代码搞到头疼过?用eval
写了段用户输入的代码,结果页面突然弹广告;想用new Function
封装动态逻辑,发现变量怎么都访问不到……今天咱们就聊聊JavaScript里的"动态执行双兄弟"——eval()
和new Function()
,用最接地气的话讲清它们的核心区别,看完这篇,你不仅能避开坑,还能和面试官唠明白背后的逻辑~
一、动态执行代码的"翻车现场"
先讲个我上周踩的坑:给客户做表单验证工具,需要根据用户输入的规则动态执行校验逻辑。用户提交了一段规则:
"return username === 'admin' && password === '123456'"
我用eval
直接执行这段代码,结果用户偷偷加了句document.write('<script src="恶意链接"></script>')
,页面瞬间被篡改……后来改用new Function
,发现怎么都访问不到外层的username
变量,差点被客户骂哭~
类似的场景还有:配置化系统的动态表达式计算、低代码平台的逻辑执行……这些问题的根源,都和eval
与new Function
的执行上下文差异及安全风险有关。
二、从执行上下文看本质区别
要搞懂两者的区别,得先明白JavaScript的执行上下文(Execution Context)——它是代码运行时的"环境",包含变量对象、作用域链、this
等信息。eval
和new Function
的核心差异,就在于它们如何创建和使用这个"环境"。
1. 执行上下文的创建规则
eval
:在当前执行上下文中执行代码。它会"潜入"当前作用域,直接访问和修改外层的变量(包括函数作用域、块级作用域)。
官方文档说它是:“Evaluates JavaScript code represented as a string in the current scope.”(在当前作用域中计算字符串形式的JavaScript代码。)
new Function
:创建一个新的函数执行上下文,其作用域链仅包含全局作用域(或模块作用域)。它像一个"隔离舱",无法直接访问外层函数的局部变量(除非通过闭包或全局变量)。
2. 作用域链的差异
eval
的作用域链:与调用它的函数共享作用域链。比如在函数fn()
里调用eval
,eval
可以访问fn
的局部变量、外层函数的变量,甚至全局变量。
示例:
function demo() {
const localVar = "我是局部变量";
eval("console.log(localVar)"); // 可以访问localVar,输出:我是局部变量
}
demo();
new Function
的作用域链:仅包含全局作用域(或模块作用域)。即使在函数fn()
里创建new Function
,它也无法访问fn
的局部变量(除非变量是全局的)。
示例:
function demo() {
const localVar = "我是局部变量";
const func = new Function("console.log(localVar)"); // 报错:localVar未定义
func();
}
demo();
3. this
的指向差异
eval
中的this
:与调用eval
时的this
一致。比如在全局调用eval
,this
是window
;在对象方法中调用eval
,this
是对象实例。
示例:
const obj = {
name: "对象",
run() {
eval("console.log(this.name)"); // this指向obj
}
};
obj.run(); // 输出:对象
new Function
中的this
:在非严格模式下指向全局对象(window
/global
);严格模式下是undefined
。与创建它的作用域的this
无关。
示例:
const obj = {
name: "对象",
run() {
const func = new Function("console.log(this.name)"); // this是window(name是undefined)
func();
}
};
obj.run(); // 输出:undefined
三、代码示例:对比出真知
示例1:访问外层变量
// 测试函数:包含局部变量
function testScope() {
const a = 10;
const b = 20;
// 用eval访问局部变量
eval("console.log('eval访问a:', a)"); // 输出:eval访问a: 10
// 用new Function访问局部变量(失败)
const func = new Function("console.log('new Function访问a:', a)");
try {
func();
} catch (e) {
console.log("new Function报错:", e.message); // 输出:new Function报错: a is not defined
}
// 用new Function访问全局变量(成功)
window.c = 30; // 全局变量c
const func2 = new Function("console.log('new Function访问c:', c)");
func2(); // 输出:new Function访问c: 30
}
testScope();
示例2:修改外层变量
function testModify() {
let x = 1;
// eval修改外层变量(成功)
eval("x = x + 1;");
console.log("eval修改后x:", x); // 输出:eval修改后x: 2
// new Function修改外层变量(失败,只能修改全局变量)
const func = new Function("x = x + 1;");
func();
console.log("new Function修改后x:", x); // 输出:new Function修改后x: 2(x还是2?因为func修改的是全局x)
console.log("全局x:", window.x); // 输出:全局x: NaN(因为全局x未定义,x+1是NaN)
}
testModify();
示例3:this
指向对比
// 全局作用域调用
eval("console.log('eval全局this:', this === window)"); // 输出:true
const func1 = new Function("console.log('new Function全局this:', this === window)");
func1(); // 输出:true(非严格模式)
// 对象方法中调用
const obj = {
name: "测试对象",
evalTest() {
eval("console.log('eval对象this:', this.name)"); // 输出:测试对象
},
funcTest() {
const func = new Function("console.log('new Function对象this:', this.name)");
func(); // 输出:undefined(this是window,无name属性)
}
};
obj.evalTest();
obj.funcTest();
四、一张表总结核心差异
对比项 | eval() | new Function() |
---|---|---|
执行上下文 | 当前作用域(与调用者共享) | 新的函数作用域(仅全局作用域) |
访问外层变量 | 可以(直接访问函数/块级作用域) | 不可以(仅能访问全局变量) |
修改外层变量 | 可以(直接修改函数/块级作用域变量) | 不可以(只能修改全局变量) |
this 指向 | 与调用者的this 一致 | 非严格模式:全局对象;严格模式:undefined |
安全风险 | 极高(可访问所有作用域变量) | 较高(但无法访问局部变量) |
性能 | 较差(破坏引擎优化) | 较差(每次创建新函数) |
适用场景 | 极少数(如必须操作当前作用域) | 动态创建函数(需隔离作用域) |
五、面试题回答方法
正常回答(结构化):
“eval()
与new Function()
的执行上下文差异及安全防护主要体现在:
1、执行上下文:eval
在当前作用域执行,可访问和修改外层变量;new Function
在新的全局作用域执行,无法访问局部变量。
2、this
指向:eval
的this
与调用者一致;new Function
的this
是全局对象(非严格模式)。
3、安全风险:eval
风险更高(可访问所有作用域),易导致XSS攻击;new Function
风险较低(仅能访问全局)。
4、防护措施:避免使用、严格校验输入、使用沙盒、替换为Function.prototype.bind
等安全方案。”
大白话回答(接地气):
“eval
就像‘拆家小能手’——它直接钻进你家(当前作用域),能翻你抽屉(局部变量)、改你存折(修改变量)。比如你在函数里用eval
,它能随便动你函数里的变量。
new Function
像‘隔离房住户’——它住在独立的小房间(全局作用域),看不到你家抽屉(局部变量),只能翻小区公共信箱(全局变量)。
安全方面,eval
太危险,给它段恶意代码,它能拆你整个家(XSS攻击);new Function
虽然安全点,但也别随便放陌生人进去(用户输入)。”
六、总结:3个使用原则+4层安全防护
3个使用原则:
1)能不用就不用:现代JS的Proxy
、模板字符串、动态导入等方案,基本能替代动态执行需求;
2)优先new Function
:需动态执行代码时,new Function
比eval
更安全(无法访问局部变量);
3)严格校验输入:必须使用时,对输入的代码字符串做严格校验(如正则匹配允许的关键字)。
4层安全防护:
1)输入白名单校验:只允许特定关键字(如return
、数学运算符),禁止document
、window
等危险对象。
示例(校验函数):
function validateCode(code) {
const allowedKeywords = /^[\w\s\+\-\*\/\(\)\.]+\;?$/; // 允许数字、字母、运算符等
return allowedKeywords.test(code);
}
2)使用strict mode
:在eval
或new Function
中启用严格模式,限制this
为undefined
,禁止with
等危险语法。
示例:
eval("'use strict'; console.log(this)"); // 输出:undefined
const func = new Function("'use strict'; console.log(this)");
func(); // 输出:undefined
3)沙盒环境:用iframe
或Worker
创建隔离的沙盒,限制代码的访问权限(如禁止访问DOM)。
示例(iframe
沙盒):
<iframe id="sandbox" sandbox="allow-scripts"></iframe>
<script>
const iframe = document.getElementById('sandbox');
const code = "console.log('沙盒内执行:', document.title)"; // 无法访问外层document
iframe.contentWindow.eval(code);
</script>
4)替代方案:用Function.prototype.bind
、模板字符串等安全方式实现动态逻辑。
示例(模板字符串替代):
const username = "admin";
const password = "123456";
const rule = "username === 'admin' && password === '123456'";
const result = eval(rule); // 危险!改用:
const result = new Function("username", "password", `return ${rule}`)(username, password);
七、扩展思考:4个高频问题解答
问题1:eval
和new Function
的性能哪个更差?
解答:两者性能都较差,但eval
更糟。因为eval
会破坏JavaScript引擎的优化(如JIT编译),导致代码无法被高效执行。new Function
每次创建新函数,也会有一定开销,但比eval
略好。
问题2:eval
可以访问块级作用域(let
/const
)吗?
解答:可以!eval
在严格模式下可以访问块级作用域的变量。
示例:
function testBlockScope() {
{
const blockVar = "块级变量";
eval("'use strict'; console.log(blockVar)"); // 输出:块级变量(严格模式)
}
}
testBlockScope();
问题3:new Function
可以访问闭包变量吗?
解答:不能直接访问,但可以通过参数传递闭包变量。
示例:
function createFunc() {
const closureVar = "闭包变量";
// 通过参数传递闭包变量
return new Function("param", "console.log(param)");
}
const func = createFunc();
func(closureVar); // 输出:闭包变量(需要手动传递)
问题4:现代框架(如React/Vue)禁止使用eval
吗?
解答:框架本身不禁止,但打包工具(如Webpack)会将eval
标记为危险操作,部分安全策略(CSP)也会禁止eval
。例如,React的dangerouslySetInnerHTML
就不推荐与eval
配合使用。
结尾:动态执行,安全第一
eval
和new Function
就像两把锋利的刀——用好了能解决复杂问题,用不好会割伤自己。记住:
- 能不用就不用,优先选择安全的替代方案;
- 必须用时,做好输入校验和沙盒隔离;
转自:在JavaScript中eval() 和 new Function() 的执行上下文差异及安全防护?_new function的执行上下文-CSDN博客