开启掘金成长之旅!这是我参与「掘金日新计划 · 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功能会被完全禁止。所以不能使用。