无论是JavaScript或其他编程语言,都会存在“作用域”的概念。它规定了代码对变量的访问权限以及如何查找变量。词法作用域分为静态作用域和动态作用域,JavaScript所采用的是静态词法作用域。
何为静态词法作用域?
1 | var value = 1; |
test1函数实在test2函数的作用域中调用的,value已经在test2函数作用域中存在,但是test1任然输出了1,也就是全局作用于中的value值。
这就是静态作用域最好的表示,在JS引擎对代码进行静态词法分析的时候,已经将每个变量的词法作用域确定下来了,且在动态运行的过程中不随着调用栈的变化而改变。反之动态词法作用域相信大家也能理解了。
欺骗词法作用域
JavaScript其实还能够在程序运行的过程中来修改词法作用域,但因为性能原因不建议使用。
1. eval
eval函数可以接受一个字符串作为参数,并将其内容转换为代码存放于程序的相应位置。可想而知,这种动态生成的代码如果带有变量声明,那么程序的词法作用域环境也会发生相应的变化。1
2
3
4
5
6var b = 1;
function foo(str){
eval(str)
console.log(b) // 2
}
foo("var b = 2;")
在foo函数中,对str参数进行了执行,相当于当前行变成了 var b = 2,最终输出了2。这行代码执行后声明了一个b变量,对上层作用域的变量b进行了遮蔽。所以console.log执行时,只在foo的内部找到了b,无法找到外部的b。
2. with
with用来避免重复引用对象的快捷方式,例如:1
2
3
4
5
6
7
8
9
10
11let obj = {
a: 1,
b: 2,
c: 3
}
obj.a / obj.a / obj.c // 访问起来吃力
with(obj){
a= 3;
b= 4;
c= 5;
}
如此,我们在操作对象的时候就可以省很多力,那么如何通过with来更改词法作用域?1
2
3
4
5
6
7
8
9
10
11
12function setData(obj){
with(obj){
a = 10
}
}
var o1 = { a: 3}
var o2 = { b: 1}
setData(o1)
console.log(o1.a) // 10
setData(o2)
console.log(o2.a) // undifined
console.log(a) // 10 发生泄漏
在with的作用域中,如果对obj没有的属性进行赋值,便会在全局作用域下生成新的变量(修改了词法作用域)。
总结
JavaScript是一种“解释型”语言,在到达浏览器之前不会进行编译和构建。所以它追求的是每一行能够尽快的编译执行完毕,不会对DOM造成阻塞。所以在编译的过程中要尽可能的优化代码, 减少运行时资源的消耗。
而我们使用类似eval/with去欺骗(修改)了词法作用域,会让浏览器减少相应代码块的优化,因为即使做了静态词法作用域分析,也会在将来被这类操作修改,没有意义。

