数据库|「建议收藏」词法作用域( 三 )


foo(o2)
console.log(o2) // Object { b: 3 

console.log(a)// a 被泄漏到全局作用域上了!
在这个例子中创建了 o1 和 o2 两个对象 , 其中一个具有 a 属性 , 另外一个没有 。 foo 函数接受了一个 obj 参数 , 该参数是一个对象的引用 , 并对这个对象执行了 with 方法 。
在 with 内部 , 我们写的代码看起来只是对变量 a 进行简单的词法引用 , 实际上就是一个 LHS 引用 , 并将 2 赋值给它 。


LHS 查找如果最终没有找到 , 会在全局作用域里面创建一个 。
当我们将 o1 传递进去 , a = 2 赋值操作找到了 o1.a 并将 2 赋值给它 , 这在后面的 console.log 中可以得到体现 。 而当 o2 传递进去 ,o2 并没有 a 属性 , 因此不会创建这个属性 , o2.a 保持 undefined 。
但是注意到一个奇怪的副作用 , 实际上 a = 2 赋值操作创建了一个全局的变量 a , 这是怎么回事?
with 可以将一个没有属性的对象处理为一个完全隔离的词法作用域 , 因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符 。
eval 函数如果接受了含一个声明的代码 , 就会修改其所处的词法作用域 , 而 with 声明实际上是根据你传递给它的对象 , 凭空创建了一个全新的词法作用域 。
可以这样理解 , 当我们传递 o1 给 with 时 , with 所声明的作用域是 o1 , 而这个作用域中包含有一个同 o1.a 属性的标识符 。 但当我们将 o2 作为作用域时 , 其中并没有 a 标识符 , 因此进行了正常 LHS 标识符查找 。
o2 的作用域、foo 的作用域和全局作用域都没有找到标识符 a , 因此当 a = 2 执行时 , 自动地创建了一个全局变量 。
with 这种将对象及其属性放进一个作用域并同时分配标识符的行为很令人费解 。
性能eval 和 with 会在运行时修改或创建新的作用域 , 以此欺骗其他在书写时定义的词法作用域 。
你可能会问 , 那又怎么样呢?如果它们能实现更复杂的功能 , 并且代码更具有扩展性 , 难道不是非常好的功能吗?
答案是否定的 。
JavaScript 引擎会在编译阶段进行数项的性能优化 。 其中有些优化依赖于能够根据代码词法进行静态分析 , 并预先确定所有变量和函数定义的位置 , 才能在执行过程中快速找到标识符 。
但如果在引擎代码中发现了 eval 或 with , 它只能简单地假设关于标识符位置的判断都是无效的 , 因为无法在词法分析阶段明确知道 eval 会接收到什么代码 , 这些代码会如何对作用域进行修改 , 也无法知道传递给 with 用来创建新的词法作用域对象的内容到底是什么 。
最悲观的情况是如果出现了 eval 或 with , 所有的优化可能都是无意义的 , 因此最简单的方式就是完全不做任何优化 。
如果代码中大量使用了 eval 或 with , 那么运行起来一定非常的缓慢 。 无论引擎多聪明 , 试图将这些悲观的情况限制在最小范围内 , 也无法避免如果没有这些优化 , 代码会运行得更慢这个事实 。