Eval:执行字符串内的代码

内建(built-in)函数 eval 让我们能够执行字符串内的代码。

语法如下:

let result = eval(code);

比如:

let code = 'alert("Hello")';
eval(code); // Hello

这样的字符串可能比较长,其中包含换行符(line breaks)、函数声明(function declarations)和变量,等等。

eval 返回字符串中最后一个语句的结果。

比如:

let value = eval('1+1');
alert(value); // 2
let value = eval('let i = 0; ++i');
alert(value); // 1

字符串内的代码在当前词法环境(lexical environment)下执行,因此能访问外部变量:

let a = 1;

function f() {
  let a = 2;

  eval('alert(a)'); // 2
}

f();

我们也能对外部变量重新赋值:

let x = 5;
eval("x = 10");
alert(x); // 10, 变量的值改变了

严格模式(strict mode)下,eval 有属于自己的词法环境,因此我们不能从外部访问在 eval 中声明的函数和变量:

// 提示: 网站内可运行的代码默认启用 'use strict'

eval("let x = 5; function f() {}");

alert(typeof x); // undefined (不存在该变量)
// f 函数也不可从外部访问

如果不用严格模式,eval 没有属于自己的词法环境,因此我们能在外部访问 x 变量和 f 函数。

使用“eval”

现代编程中,eval 已不常用。人们经常说“eval is evil”.

原因很简单:JavaScript 曾经很难用,很多操作只能用 eval 来完成,不过这是十年前的事了。

如今几乎找不到理由来用 eval 了。如果有人用,那么可能要换用现代语言构造(language construct)或者 JavaScript Module 了。

注意,eval 访问外部函数会产生副作用(side-effects)。

代码压缩工具(minifier,在 JS 进入生产环境前对其进行压缩的工具)将局部变量重命名,使其更短(比如 ab,等等),这样代码体积就变小了。这种方式通常比较安全,但在使用 eval 的情况下就不一样了,这是因为局部变量可能会被 eval 中的代码访问到。因此压缩器不会对所有可能被 eval 访问的变量作重命名。这样会导致代码压缩率降低。

eval 内获取外部局部变量不是良好的编程习惯,这会使维护代码变得更加困难。

有两种方法来完全避免这类问题。

如果字符串中的代码不访问外部变量,调用 window.eval(...)

这样,代码便会在全局作用域(global scope)内执行:

let x = 1;
{
  let x = 5;
  window.eval('alert(x)'); // 1 (全局变量)
}

如果 eval 内代码需要访问局部变量,我们可以使用 new Function,将此变量作为参数传递。

let f = new Function('a', 'alert(a)');

f(5); // 5

对于 new Function,可参考 "new Function" 语法。该构造函数接收字符串,返回位于全局作用域下的函数,因此该函数无法访问局部变量。然而如以上一例,向 new Function 显式传递该变量会使代码变得容易理解。

总结

eval(code) 会执行字符串内的代码并返回最后一个语句的结果。

  • 现代 JavaScript 不常用该函数,通常也没必要用。
  • 该函数可以访问外部局部变量,但这不是个好习惯。
  • 取而代之,使用 window.eval(code) 以使代码在全局作用域下执行。
  • 或者,如果代码需要从外部作用域获取数据,请使用 new Function 并将该数据作为参数传递给函数。

任务

重要程度: 4

创建一个计算器,提示用户输入算术表达式,并返回结果。

没必要检查表达式是否正确,求值并返回结果即可。

运行 demo

让我们用 eval 函数来计算数学表达式:

let expr = prompt("Type an arithmetic expression?", '2*3+2');

alert( eval(expr) );

不过,用户也可以输入文本或者代码。

安全起见,我们可以用 regular expression 来检查 eval 函数以限制用户只能输入算数表达式,这样输入的内容只能包含数字和运算符。

教程路线图

评论

在评论之前先阅读本内容…
  • 如果你发现教程有错误,或者有其他需要修改和提升的地方 — 请 提交一个 GitHub issue 或 pull request,而不是在这评论。
  • 如果你对教程的内容有不理解的地方 — 请详细说明。
  • 使用 <code> 标签插入只有几个词的代码,插入多行代码可以使用 <pre> 标签,对于超过 10 行的代码,建议你使用沙箱(plnkrJSBincodepen…)