JavaScript的静态词法作用域

JavaScript的静态词法作用域

无论是JavaScript或其他编程语言,都会存在“作用域”的概念。它规定了代码对变量的访问权限以及如何查找变量。词法作用域分为静态作用域和动态作用域,JavaScript所采用的是静态词法作用域。

何为静态词法作用域?

1
2
3
4
5
6
7
8
9
var value = 1;
function test1() {
console.log(value) // 1
}
function test2(){
var value = 2
test1(1);
}
test2()

test1函数实在test2函数的作用域中调用的,value已经在test2函数作用域中存在,但是test1任然输出了1,也就是全局作用于中的value值。

这就是静态作用域最好的表示,在JS引擎对代码进行静态词法分析的时候,已经将每个变量的词法作用域确定下来了,且在动态运行的过程中不随着调用栈的变化而改变。反之动态词法作用域相信大家也能理解了。

欺骗词法作用域

JavaScript其实还能够在程序运行的过程中来修改词法作用域,但因为性能原因不建议使用。

1. eval

eval函数可以接受一个字符串作为参数,并将其内容转换为代码存放于程序的相应位置。可想而知,这种动态生成的代码如果带有变量声明,那么程序的词法作用域环境也会发生相应的变化。

1
2
3
4
5
6
var 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
11
let 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
12
function 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去欺骗(修改)了词法作用域,会让浏览器减少相应代码块的优化,因为即使做了静态词法作用域分析,也会在将来被这类操作修改,没有意义。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×