eval()
警告: 从字符串执行 JavaScript 存在巨大的安全风险。恶意行为者在使用 eval() 时运行任意代码太容易了。请参阅下方的 切勿直接使用 eval()!。
eval() 函数评估表示为字符串的 JavaScript 代码,并返回其完成值。源代码被解析为脚本。
试一试
console.log(eval("2 + 2"));
// Expected output: 4
console.log(eval(new String("2 + 2")));
// Expected output: 2 + 2
console.log(eval("2 + 2") === eval("4"));
// Expected output: true
console.log(eval("2 + 2") === eval(new String("2 + 2")));
// Expected output: false
语法
eval(script)
参数
返回值
评估给定代码的完成值。如果完成值为空,则返回 undefined。如果 script 不是字符串原始值,eval() 将不变地返回该参数。
异常
抛出代码评估期间发生的任何异常,包括如果 script 未能解析为脚本而抛出的 SyntaxError。
描述
eval() 是全局对象的一个函数属性。
eval() 函数的参数是一个字符串。它将源代码字符串作为脚本主体进行评估,这意味着允许语句和表达式。它返回代码的完成值。对于表达式,它是表达式的计算结果。许多语句和声明也具有完成值,但结果可能令人惊讶(例如,赋值的完成值是赋值的值,但 let 的完成值是 undefined),因此建议不要依赖语句的完成值。
在严格模式下,声明名为 eval 的变量或重新赋值 eval 会导致 SyntaxError。
"use strict";
const eval = 1; // SyntaxError: Unexpected eval or arguments in strict mode
如果 eval() 的参数不是字符串,eval() 将不变地返回该参数。在以下示例中,传递 String 对象而不是原始值会导致 eval() 返回 String 对象而不是评估字符串。
eval(new String("2 + 2")); // returns a String object containing "2 + 2"
eval("2 + 2"); // returns 4
要以通用方式解决此问题,您可以在将其传递给 eval() 之前 将参数强制转换为字符串。
const expression = new String("2 + 2");
eval(String(expression)); // returns 4
直接和间接 eval
eval() 调用有两种模式:直接 eval 和 间接 eval。顾名思义,直接 eval 是指通过 eval(...) 直接 调用全局 eval 函数。其他一切,包括通过别名变量、通过成员访问或其他表达式,或通过可选链 ?. 运算符调用,都是间接的。
// Direct call
eval("x + y");
// Indirect call using the comma operator to return eval
(0, eval)("x + y");
// Indirect call through optional chaining
eval?.("x + y");
// Indirect call using a variable to store and return eval
const myEval = eval;
myEval("x + y");
// Indirect call through member access
const obj = { eval };
obj.eval("x + y");
间接 eval 可以看作代码在单独的 <script> 标签中被评估。这意味着
-
间接 eval 在全局作用域而不是局部作用域中工作,并且被评估的代码无法访问调用它的作用域中的局部变量。
jsfunction test() { const x = 2; const y = 4; // Direct call, uses local scope console.log(eval("x + y")); // Result is 6 // Indirect call, uses global scope console.log(eval?.("x + y")); // Throws because x is not defined in global scope } -
间接
eval不继承周围上下文的严格性,并且仅当源字符串本身包含"use strict"指令时才处于 严格模式。jsfunction nonStrictContext() { eval?.(`with (Math) console.log(PI);`); } function strictContext() { "use strict"; eval?.(`with (Math) console.log(PI);`); } function strictContextStrictEval() { "use strict"; eval?.(`"use strict"; with (Math) console.log(PI);`); } nonStrictContext(); // Logs 3.141592653589793 strictContext(); // Logs 3.141592653589793 strictContextStrictEval(); // Uncaught SyntaxError: Strict mode code may not include a with statement另一方面,直接 eval 继承调用上下文的严格性。
jsfunction nonStrictContext() { eval(`with (Math) console.log(PI);`); } function strictContext() { "use strict"; eval(`with (Math) console.log(PI);`); } function strictContextStrictEval() { "use strict"; eval(`"use strict"; with (Math) console.log(PI);`); } nonStrictContext(); // Logs 3.141592653589793 strictContext(); // Uncaught SyntaxError: Strict mode code may not include a with statement strictContextStrictEval(); // Uncaught SyntaxError: Strict mode code may not include a with statement -
如果源字符串未在严格模式下解释,则
var声明的变量和 函数声明 将进入周围作用域——对于间接 eval,它们成为全局变量。如果是在严格模式上下文中的直接 eval,或者如果eval源字符串本身处于严格模式,则var和函数声明不会“泄露”到周围作用域中。js// Neither context nor source string is strict, // so var creates a variable in the surrounding scope eval("var a = 1;"); console.log(a); // 1 // Context is not strict, but eval source is strict, // so b is scoped to the evaluated script eval("'use strict'; var b = 1;"); console.log(b); // ReferenceError: b is not defined function strictContext() { "use strict"; // Context is strict, but this is indirect and the source // string is not strict, so c is still global eval?.("var c = 1;"); // Direct eval in a strict context, so d is scoped eval("var d = 1;"); } strictContext(); console.log(c); // 1 console.log(d); // ReferenceError: d is not defined -
直接 eval 可以访问额外的上下文表达式。例如,在函数体内,可以使用
new.targetjsfunction Ctor() { eval("console.log(new.target)"); } new Ctor(); // [Function: Ctor]
切勿直接使用 eval()!
直接使用 eval() 存在多个问题
eval()以调用者的权限执行传递给它的代码。如果您使用可能受恶意方影响的字符串运行eval(),您最终可能会在用户的机器上以您的网页/扩展的权限运行恶意代码。更重要的是,允许第三方代码访问调用eval()的作用域(如果是直接 eval)可能导致读取或更改局部变量的攻击。eval()比替代方案慢,因为它必须调用 JavaScript 解释器,而许多其他构造已被现代 JS 引擎优化。- 现代 JavaScript 解释器将 JavaScript 转换为机器代码。这意味着任何变量命名概念都将被消除。因此,任何使用
eval()的行为都将强制浏览器进行耗时且昂贵的变量名称查找,以确定变量在机器代码中的位置并设置其值。此外,通过eval()可以向该变量引入新内容,例如更改该变量的类型,迫使浏览器重新评估所有生成的机器代码以进行补偿。 - 如果作用域被
eval()传递性依赖,最小化器会放弃所有最小化,否则eval()无法在运行时读取正确的变量。
在许多情况下,可以优化或完全避免使用 eval() 或相关方法。
使用间接 eval()
考虑以下代码
function looseJsonParse(obj) {
return eval(`(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Map() }"));
简单地使用间接 eval 并强制严格模式可以使代码好得多
function looseJsonParse(obj) {
return eval?.(`"use strict";(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Map() }"));
上面两段代码可能看起来工作方式相同,但它们并非如此;第一个使用直接 eval 的代码存在多个问题。
-
由于更多的作用域检查,它要慢得多。请注意评估字符串中的
c: new Map()。在间接 eval 版本中,对象是在全局作用域中评估的,因此解释器可以安全地假定Map指的是全局Map()构造函数而不是名为Map的局部变量。然而,在使用直接 eval 的代码中,解释器无法假定这一点。例如,在以下代码中,评估字符串中的Map并不指window.Map()。jsfunction looseJsonParse(obj) { class Map {} return eval(`(${obj})`); } console.log(looseJsonParse(`{ a: 4 - 1, b: function () {}, c: new Map() }`));因此,在
eval()版本的代码中,浏览器被迫进行昂贵的查找调用,以检查是否存在任何名为Map()的局部变量。 -
如果不使用严格模式,
eval()源中的var声明会成为周围作用域中的变量。如果字符串是从外部输入获取的,这会导致难以调试的问题,特别是当存在同名变量时。 -
直接 eval 可以读取和修改周围作用域中的绑定,这可能导致外部输入损坏局部数据。
-
当使用直接
eval时,特别是当eval源无法被证明是严格模式时,引擎和构建工具必须禁用与内联相关的所有优化,因为eval()源可能依赖其周围作用域中的任何变量名。
然而,使用间接 eval() 不允许传递除现有全局变量以外的额外绑定供被评估的源读取。如果您需要指定被评估源应有权访问的附加变量,请考虑使用 Function() 构造函数。
使用 Function() 构造函数
Function() 构造函数与上面的间接 eval 示例非常相似:它也在全局作用域中评估传递给它的 JavaScript 源,而不读取或修改任何局部绑定,因此允许引擎执行比直接 eval() 更多的优化。
eval() 和 Function() 之间的区别在于,传递给 Function() 的源字符串被解析为函数体,而不是脚本。有一些细微差别——例如,您可以在函数体的顶层使用 return 语句,但不能在脚本中使用。
如果您希望通过将变量作为参数绑定来在 eval 源中创建局部绑定,则 Function() 构造函数很有用。
function add(a, b) {
return a + b;
}
function runCodeWithAddFunction(obj) {
return Function("add", `"use strict";return (${obj});`)(add);
}
console.log(runCodeWithAddFunction("add(5, 7)")); // 12
eval() 和 Function() 都隐式评估任意代码,并且在严格的 CSP 设置中是禁止的。对于常见用例,还有其他更安全(更快!)的 eval() 或 Function() 替代方案。
使用方括号访问器
您不应该使用 eval() 动态访问属性。考虑以下示例,其中直到代码执行才知道要访问的对象的属性。这可以通过 eval() 完成
const obj = { a: 20, b: 30 };
const propName = getPropName(); // returns "a" or "b"
const result = eval(`obj.${propName}`);
然而,这里不需要 eval()——实际上,它更容易出错,因为如果 propName 不是有效的标识符,它会导致语法错误。此外,如果 getPropName 不是您控制的函数,这可能导致任意代码执行。相反,使用 属性访问器,它更快、更安全
const obj = { a: 20, b: 30 };
const propName = getPropName(); // returns "a" or "b"
const result = obj[propName]; // obj["a"] is the same as obj.a
您甚至可以使用此方法访问后代属性。使用 eval(),这看起来像
const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"
const result = eval(`obj.${propPath}`); // 0
这里避免使用 eval() 可以通过拆分属性路径并遍历不同的属性来完成
function getDescendantProp(obj, desc) {
const arr = desc.split(".");
while (arr.length) {
obj = obj[arr.shift()];
}
return obj;
}
const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"
const result = getDescendantProp(obj, propPath); // 0
以这种方式设置属性也类似
function setDescendantProp(obj, desc, value) {
const arr = desc.split(".");
while (arr.length > 1) {
obj = obj[arr.shift()];
}
return (obj[arr[0]] = value);
}
const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"
const result = setDescendantProp(obj, propPath, 1); // obj.a.b.c is now 1
但是,请注意,使用方括号访问器与不受限制的输入也不安全——它可能导致 对象注入攻击。
使用回调
JavaScript 具有 头等函数,这意味着您可以将函数作为参数传递给其他 API,将它们存储在变量和对象的属性中等等。许多 DOM API 都考虑到了这一点,因此您可以(并且应该)编写
// Instead of setTimeout("…", 1000) use:
setTimeout(() => {
// …
}, 1000);
// Instead of elt.setAttribute("onclick", "…") use:
elt.addEventListener("click", () => {
// …
});
闭包 也是创建参数化函数而无需连接字符串的一种有用方法。
使用 JSON
如果您要调用 eval() 的字符串包含数据(例如,一个数组:"[1, 2, 3]"),而不是代码,您应该考虑切换到 JSON,它允许字符串使用 JavaScript 语法的子集来表示数据。
请注意,由于 JSON 语法相对于 JavaScript 语法是有限的,因此许多有效的 JavaScript 字面量将无法解析为 JSON。例如,JSON 中不允许尾随逗号,并且对象字面量中的属性名称(键)必须用引号括起来。请务必使用 JSON 序列化器生成稍后将解析为 JSON 的字符串。
传递经过仔细限制的数据而不是任意代码通常是一个好主意。例如,一个旨在抓取网页内容的扩展程序可以将抓取规则定义为 XPath 而不是 JavaScript 代码。
示例
使用 eval()
在以下代码中,包含 eval() 的两个语句都返回 42。第一个评估字符串 "x + y + 1";第二个评估字符串 "42"。
const x = 2;
const y = 39;
const z = "42";
eval("x + y + 1"); // 42
eval(z); // 42
eval() 返回语句的完成值
eval() 返回语句的完成值。对于 if,它将是评估的最后一个表达式或语句。
const str = "if (a) { 1 + 1 } else { 1 + 2 }";
let a = true;
let b = eval(str);
console.log(`b is: ${b}`); // b is: 2
a = false;
b = eval(str);
console.log(`b is: ${b}`); // b is: 3
以下示例使用 eval() 评估字符串 str。此字符串由 JavaScript 语句组成,如果 x 为五,则将 z 赋值为 42,否则将 0 赋值给 z。当执行第二个语句时,eval() 将导致这些语句被执行,并且它还将评估这组语句并返回赋值给 z 的值,因为赋值的完成值是被赋值的值。
const x = 5;
const str = `if (x === 5) {
console.log("z is 42");
z = 42;
} else {
z = 0;
}`;
console.log("z is ", eval(str)); // z is 42 z is 42
如果您分配多个值,则返回最后一个值。
let x = 5;
const str = `if (x === 5) {
console.log("z is 42");
z = 42;
x = 420;
} else {
z = 0;
}`;
console.log("x is", eval(str)); // z is 42 x is 420
eval() 作为定义函数的字符串需要 "(" 和 ")" 作为前缀和后缀
// This is a function declaration
const fctStr1 = "function a() {}";
// This is a function expression
const fctStr2 = "(function b() {})";
const fct1 = eval(fctStr1); // return undefined, but `a` is available as a global function now
const fct2 = eval(fctStr2); // return the function `b`
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-eval-x |
浏览器兼容性
加载中…