eval and with

53 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情

在上篇中,我们说到了词法作用域,它是由我们编写的代码位置顺序来决定的。但也存在特殊情况,像eval或with作用下,会欺骗编译阶段,破坏了词法作用域的规则。下面我们具体来看看它们是如何破坏的。

eval

考虑如下代码:

function foo (str) {
  eval(str)
  var b = 2
  console.log(a + b)
}
foo('var a = 1') // 3

在编译阶段,其实是不清楚是否有变量a的,因为eval函数的参数是传入的,在非严格模式下,虽然能正常输出3,但如果是在严格模式下,会报错ReferenceError,变量a是找不到的。

并且这样有个很严重的问题,表达式var a = 1既然能传入函数foo内,那它也可以传入任何函数内来扰乱原本函数作用域的值。遮蔽效应会取最近作用域变量的值,传入的表达式很可能会空降遮蔽原来的值而使程序运行不稳定。所以eval是不推荐使用的。

with

with通常被当做引用同一对象中多个属性的快捷方式,可以不需要重复引用对象本身。

考虑如下代码:

var obj = {
  a: 1,
  b: 2,
  c: 3
}
// 重复引用对象obj
obj.a = 4
obj.b = 5
obj.c = 6
// with
with (obj) {
  a: 4,
  b: 5,
  c: 6
}

这样的话,是比较简洁的。

但我们再看段代码:

function foo (obj) {
  with (obj) {
    a: 100
  }
}
var obj1 = {
  a: 1
}
foo(obj1)
console.log(obj1.a) // 100
var obj2 = {
  b: 1
}
foo(obj2)
console.log(obj2.a) // undefined
console.log(a) // 100

看到最后两个输出,感觉好奇怪啊,严重超出了我们的预期,特别是最后输出变量a,是100,那就说明with内部的值100泄露到全局作用域去了。

刚foo函数传入对象obj2的时候,输出undefined,说明with只会给对象中已经存在的属性重新赋值,若传入的对象中不存在,则undefined。并且会一直往上作用域中查询,直到全局作用域都没有找到的话,就在全局作用域下声明了个全局变量a并进行了赋值。

在严格模式下,with功能会被完全禁止。所以不能使用。