JavaScript中,为什么eval和with会有性能问题?
词法作用域
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段 基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它 们进行查找。
eval(..) 和 with
JavaScript 中有两个机制可以“欺骗”词法作用域:eval(..) 和 with。前者可以对一段包 含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在运行时)。后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作 用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。
这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认 为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。不要使用它们。
性能
eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。 你可能会问,那又怎样呢?如果它们能实现更复杂的功能,并且代码更具有扩展性,难道不是非常好的功能吗?答案是否定的。
JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的 词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。
但如果引擎在代码中发现了 eval(..) 或 with,它只能简单地假设关于标识符位置的判断 都是无效的,因为无法在词法分析阶段明确知道 eval(..) 会接收到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给 with 用来创建新词法作用域的对象的内容到底是什么。
最悲观的情况是如果出现了 eval(..) 或 with,所有的优化可能都是无意义的,因此最简单的做法就是完全不做任何优化。
如果代码中大量使用 eval(..) 或 with,那么运行起来一定会变得非常慢。无论引擎多聪明,试图将这些悲观情况的副作用限制在最小范围内,也无法避免如果没有这些优化,代码会运行得更慢这个事实。
摘抄自《你不知道的JavaScript上卷》