eval()

警告:从字符串执行 JavaScript 存在巨大的安全风险。当你使用 eval() 时,坏人很容易运行任意代码。参见下文 切勿直接使用 eval()!

¥Warning: Executing JavaScript from a string is an enormous security risk. It is far too easy for a bad actor to run arbitrary code when you use eval(). See Never use direct eval()!, below.

eval() 函数计算以字符串表示的 JavaScript 代码并返回其完成值。源被解析为脚本。

¥The eval() function evaluates JavaScript code represented as a string and returns its completion value. The source is parsed as a script.

Try it

语法

¥Syntax

js
eval(script)

参数

¥Parameters

script

表示 JavaScript 表达式、语句或语句序列的字符串。该表达式可以包含现有对象的变量和属性。它将被解析为脚本,因此不允许 import 声明(只能存在于模块中)。

返回值

¥Return value

评估给定代码的完成值。如果完成值为空,则返回 undefined。如果 script 不是字符串基元,则 eval() 不改变地返回参数。

¥The completion value of evaluating the given code. If the completion value is empty, undefined is returned. If script is not a string primitive, eval() returns the argument unchanged.

例外情况

¥Exceptions

抛出在代码评估期间发生的任何异常,包括 SyntaxError(如果 script 无法解析为脚本)。

¥Throws any exception that occurs during evaluation of the code, including SyntaxError if script fails to be parsed as a script.

描述

¥Description

eval() 是全局对象的函数属性。

¥eval() is a function property of the global object.

eval() 函数的参数是一个字符串。它将把源字符串评估为脚本主体,这意味着语句和表达式都是允许的。它返回代码的完成值。对于表达式,它是表达式求值的值。许多语句和声明也有完成值,但结果可能会令人惊讶(例如,赋值的完成值是分配的值,但 let 的完成值是未定义的),因此建议不要依赖语句' 完成值。

¥The argument of the eval() function is a string. It will evaluate the source string as a script body, which means both statements and expressions are allowed. It returns the completion value of the code. For expressions, it's the value the expression evaluates to. Many statements and declarations have completion values as well, but the result may be surprising (for example, the completion value of an assignment is the assigned value, but the completion value of let is undefined), so it's recommended to not rely on statements' completion values.

在严格模式下,声明名为 eval 的变量或重新分配 evalSyntaxError

¥In strict mode, declaring a variable named eval or re-assigning eval is a SyntaxError.

js
"use strict";

const eval = 1; // SyntaxError: Unexpected eval or arguments in strict mode

如果 eval() 的参数不是字符串,则 eval() 将原样返回该参数。在以下示例中,传递 String 对象而不是基元会导致 eval() 返回 String 对象而不是评估字符串。

¥If the argument of eval() is not a string, eval() returns the argument unchanged. In the following example, passing a String object instead of a primitive causes eval() to return the String object rather than evaluating the string.

js
eval(new String("2 + 2")); // returns a String object containing "2 + 2"
eval("2 + 2"); // returns 4

要以通用方式解决该问题,你可以先自行 将参数强制为字符串,然后再将其传递给 eval()

¥To work around the issue in a generic fashion, you can coerce the argument to a string yourself before passing it to eval().

js
const expression = new String("2 + 2");
eval(String(expression)); // returns 4

直接和间接评估

¥Direct and indirect eval

eval() 调用有两种模式:直接评估和间接评估。直接 eval,顾名思义,是指直接用 eval(...) 调用全局 eval 函数。其他一切,包括通过别名变量、通过成员访问或其他表达式、或者通过可选链接 ?. 运算符来调用它,都是间接的。

¥There are two modes of eval() calls: direct eval and indirect eval. Direct eval, as the name implies, refers to directly calling the global eval function with eval(...). Everything else, including invoking it via an aliased variable, via a member access or other expression, or through the optional chaining ?. operator, is indirect.

js
// 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 geval = eval;
geval("x + y");

// Indirect call through member access
const obj = { eval };
obj.eval("x + y");

间接评估可以看作是在单独的 <script> 标记内评估代码。这意味着:

¥Indirect eval can be seen as if the code is evaluated within a separate <script> tag. This means:

  • 间接 eval 在全局作用域而不是局部作用域中工作,并且正在评估的代码无法访问调用它的作用域内的局部变量。
    js
    function 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" 指令时才会在 严格模式 中。
    js
    function 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
    
    另一方面,直接评估继承了调用上下文的严格性。
    js
    function 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 和函数声明不会 "leak" 进入周围范围。
    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
    
    评估字符串中的 letconst 声明始终限于该脚本。
  • 直接评估可以访问其他上下文表达式。例如,在函数体中,可以使用 new.target
    js
    function Ctor() {
      eval("console.log(new.target)");
    }
    new Ctor(); // [Function: Ctor]
    

切勿直接使用 eval()!

¥Never use direct eval()!

直接使用 eval() 会遇到多个问题:

¥Using direct eval() suffers from multiple problems:

  • eval() 使用调用者的权限执行它传递的代码。如果你使用可能受到恶意方影响的字符串运行 eval(),则最终可能会使用你的网页/扩展程序的权限在用户的计算机上运行恶意代码。更重要的是,允许第三方代码访问调用 eval() 的作用域(如果是直接 eval)可能会导致读取或更改局部变量的攻击。
  • eval() 比替代方案慢,因为它必须调用 JavaScript 解释器,而许多其他结构都由现代 JS 引擎优化。
  • 现代 JavaScript 解释器将 JavaScript 转换为机器代码。这意味着任何变量命名的概念都会被消除。因此,任何 eval() 的使用都将迫使浏览器进行长时间昂贵的变量名称查找,以找出该变量存在于机器代码中的位置并设置其值。此外,可以通过 eval() 向该变量引入新内容,例如更改该变量的类型,强制浏览器重新评估所有生成的机器代码以进行补偿。
  • 如果 eval() 传递依赖范围,则缩小器会放弃任何缩小,否则 eval() 无法在运行时读取正确的变量。

在许多情况下,可以优化或完全避免使用 eval() 或相关方法。

¥There are many cases where the use of eval() or related methods can be optimized or avoided altogether.

使用间接 eval()

¥Using indirect eval()

考虑这段代码:

¥Consider this code:

js
function looseJsonParse(obj) {
  return eval(`(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Date() }"));

简单地使用间接 eval 并强制严格模式可以使代码变得更好:

¥Simply using indirect eval and forcing strict mode can make the code much better:

js
function looseJsonParse(obj) {
  return eval?.(`"use strict";(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Date() }"));

上面的两个代码片段可能看起来以相同的方式工作,但事实并非如此;第一个使用直接评估的方法存在多个问题。

¥The two code snippets above may seem to work the same way, but they do not; the first one using direct eval suffers from multiple problems.

  • 由于范围检查更多,速度要慢得多。注意计算字符串中的 c: new Date()。在间接 eval 版本中,对象是在全局范围内求值的,因此解释器可以安全地假设 Date 引用全局 Date() 构造函数而不是名为 Date 的局部变量。然而,在使用直接 eval 的代码中,解释器不能假设这一点。例如,在以下代码中,计算的字符串中的 Date 并不引用 window.Date()
    js
    function looseJsonParse(obj) {
      function Date() {}
      return eval(`(${obj})`);
    }
    console.log(looseJsonParse(`{ a: 4 - 1, b: function () {}, c: new Date() }`));
    
    因此,在 eval() 版本的代码中,浏览器被迫进行昂贵的查找调用来检查是否有任何名为 Date() 的局部变量。
  • 如果不使用严格模式,eval() 源中的 var 声明将成为周围范围中的变量。如果从外部输入获取字符串,这会导致难以调试的问题,特别是如果存在同名的现有变量。
  • 直接 eval 可以读取和改变周围范围内的绑定,这可能会导致外部输入损坏本地数据。
  • 当使用直接 eval 时,特别是当无法证明 eval 源处于严格模式时,引擎和构建工具必须禁用与内联相关的所有优化,因为 eval() 源可以依赖于其周围范围内的任何变量名称。

但是,使用间接 eval() 不允许传递除现有全局变量之外的额外绑定以供评估的源读取。如果你需要指定评估源应有权访问的其他变量,请考虑使用 Function() 构造函数。

¥However, using indirect eval() does not allow passing extra bindings other than existing global variables for the evaluated source to read. If you need to specify additional variables that the evaluated source should have access to, consider using the Function() constructor.

使用 Function() 构造函数

¥Using the Function() constructor

Function() 构造函数与上面的间接 eval 示例非常相似:它还评估在全局范围内传递给它的 JavaScript 源代码,而不读取或改变任何本地绑定,因此允许引擎比直接 eval() 进行更多优化。

¥The Function() constructor is very similar to the indirect eval example above: it also evaluates the JavaScript source passed to it in the global scope without reading or mutating any local bindings, and therefore allows engines to do more optimizations than direct eval().

eval()Function() 之间的区别在于,传递给 Function() 的源字符串被解析为函数体,而不是脚本。有一些细微差别 - 例如,你可以在函数体的顶层使用 return 语句,但不能在脚本中使用。

¥The difference between eval() and Function() is that the source string passed to Function() is parsed as a function body, not as a script. There are a few nuances — for example, you can use return statements at the top level of a function body, but not in a script.

如果你希望通过将变量作为参数绑定传递来在 eval 源中创建本地绑定,则 Function() 构造函数非常有用。

¥The Function() constructor is useful if you wish to create local bindings within your eval source, by passing the variables as parameter bindings.

js
function Date(n) {
  return [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
    "Sunday",
  ][n % 7 || 0];
}
function runCodeWithDateFunction(obj) {
  return Function("Date", `"use strict";return (${obj});`)(Date);
}
console.log(runCodeWithDateFunction("Date(5)")); // Saturday

eval()Function() 都隐式评估任意代码,并且在严格的 CSP 设置中被禁止。对于常见用例,还有其他更安全(更快!)的 eval()Function() 替代方案。

¥Both eval() and Function() implicitly evaluate arbitrary code, and are forbidden in strict CSP settings. There are also additional safer (and faster!) alternatives to eval() or Function() for common use-cases.

使用括号访问器

¥Using bracket accessors

你不应该使用 eval() 动态访问属性。考虑以下示例,其中要访问的对象的属性在执行代码之前是未知的。这可以通过 eval() 来完成:

¥You should not use eval() to access properties dynamically. Consider the following example where the property of the object to be accessed is not known until the code is executed. This can be done with eval():

js
const obj = { a: 20, b: 30 };
const propName = getPropName(); // returns "a" or "b"

const result = eval(`obj.${propName}`);

然而,eval() 在这里不是必需的 - 事实上,它更容易出错,因为如果 propName 不是有效的标识符,就会导致语法错误。而且,如果 getPropName 不是你控制的函数,这可能会导致任意代码的执行。相反,请使用 属性访问器,它更快、更安全:

¥However, eval() is not necessary here — in fact, it's more error-prone, because if propName is not a valid identifier, it leads to a syntax error. Moreover, if getPropName is not a function you control, this may lead to execution of arbitrary code. Instead, use the property accessors, which are much faster and safer:

js
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(),这看起来像:

¥You can even use this method to access descendant properties. Using eval(), this would look like:

js
const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"

const result = eval(`obj.${propPath}`); // 0

可以通过分割属性路径并循环不同的属性来避免此处的 eval()

¥Avoiding eval() here could be done by splitting the property path and looping through the different properties:

js
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

以这种方式设置属性的工作方式类似:

¥Setting a property that way works similarly:

js
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

但是,请注意,使用无约束输入的括号访问器也不安全 - 它可能会导致 对象注入攻击

¥However, beware that using bracket accessors with unconstrained input is not safe either — it may lead to object injection attacks.

使用回调

¥Using callbacks

JavaScript 有 一流的函数,这意味着你可以将函数作为参数传递给其他 API,将它们存储在变量和对象的属性中,等等。许多 DOM API 的设计都考虑到了这一点,因此你可以(并且应该)编写:

¥JavaScript has first-class functions, which means you can pass functions as arguments to other APIs, store them in variables and objects' properties, and so on. Many DOM APIs are designed with this in mind, so you can (and should) write:

js
// Instead of setTimeout("…", 1000) use:
setTimeout(() => {
  // …
}, 1000);

// Instead of elt.setAttribute("onclick", "…") use:
elt.addEventListener("click", () => {
  // …
});

闭包 作为创建参数化函数而无需连接字符串的方法也很有用。

¥Closures are also helpful as a way to create parameterized functions without concatenating strings.

使用 JSON

¥Using JSON

如果你调用 eval() 的字符串包含数据(例如,数组:"[1, 2, 3]"),而不是代码,你应该考虑切换到 JSON,它允许字符串使用 JavaScript 语法的子集来表示数据。

¥If the string you're calling eval() on contains data (for example, an array: "[1, 2, 3]"), as opposed to code, you should consider switching to JSON, which allows the string to use a subset of JavaScript syntax to represent data.

请注意,由于与 JavaScript 语法相比,JSON 语法受到限制,因此许多有效的 JavaScript 文字不会解析为 JSON。例如,JSON 中不允许使用尾随逗号,并且对象文本中的属性名称(键)必须用引号引起来。请务必使用 JSON 序列化程序生成稍后将解析为 JSON 的字符串。

¥Note that since JSON syntax is limited compared to JavaScript syntax, many valid JavaScript literals will not parse as JSON. For example, trailing commas are not allowed in JSON, and property names (keys) in object literals must be enclosed in quotes. Be sure to use a JSON serializer to generate strings that will be later parsed as JSON.

一般来说,传递严格约束的数据而不是任意代码是一个好主意。例如,设计用于抓取网页内容的扩展可以使用 XPath 中定义的抓取规则,而不是 JavaScript 代码。

¥Passing carefully constrained data instead of arbitrary code is a good idea in general. For example, an extension designed to scrape contents of web-pages could have the scraping rules defined in XPath instead of JavaScript code.

示例

¥Examples

使用 eval()

¥Using eval()

在以下代码中,两个包含 eval() 的语句都返回 42。第一个计算字符串 "x + y + 1";第二个计算字符串 "42"

¥In the following code, both of the statements containing eval() return 42. The first evaluates the string "x + y + 1"; the second evaluates the string "42".

js
const x = 2;
const y = 39;
const z = "42";
eval("x + y + 1"); // 42
eval(z); // 42

eval() 返回语句的完成值

¥eval() returns the completion value of statements

eval() 返回语句的完成值。对于 if,它将是最后计算的表达式或语句。

¥eval() returns the completion value of statements. For if, it would be the last expression or statement evaluated.

js
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 为 5,则为 z 分配值 42,否则为 z 分配 0。当执行第二条语句时,eval() 将导致这些语句被执行,并且它还将评估语句集并返回分配给 z 的值,因为赋值的完成值就是分配的值。

¥The following example uses eval() to evaluate the string str. This string consists of JavaScript statements that assign z a value of 42 if x is five, and assign 0 to z otherwise. When the second statement is executed, eval() will cause these statements to be performed, and it will also evaluate the set of statements and return the value that is assigned to z, because the completion value of an assignment is the assigned value.

js
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

如果分配多个值,则返回最后一个值。

¥If you assign multiple values then the last value is returned.

js
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() 作为字符串定义函数需要 "(" 和 ")" 作为前缀和后缀

¥eval() as a string defining function requires "(" and ")" as prefix and suffix

js
// 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`

规范

Specification
ECMAScript Language Specification
# sec-eval-x

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看