词法作用域
简单的说,词法作用域就是定义在词法阶段的作用域。词法作用域就是由你写代码时将变量和块作用域写在哪里来决定的。因此当词法解析器处理代码时会保持作用域不变。
function foo(a){
var b = a*2;
function bar(c){
console.log(a,b,c);
}
bar(b*3)
}
foo(2); // 2,4,12
如上例子中有三层嵌套的作用域:
- 包含整个全局作用域,其中只有一个标识符:foo;
- 包含着foo所创建的作用域,其中有三个标识符:a,b,bar;
- 包含着bar所创建的作用域,其中只有一个标识符:c;
将每个作用域看成是一个气泡,作用域气泡由其对应的作用域块代码写在哪里决定的,他们之间是逐级包含。
查找
作用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置。作用域查找会在找到第一个匹配的标识符或停止。
在多层嵌套的作用域中可以定义同名的标识符,这叫做“遮蔽效应”。
无论函数在哪里调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
欺骗词法
前面说过关于词法作用域是在你写代码时,根据函数声明的位置来定义的,那么在运行的时候是否有办法来“修改”词法作用域呢。
欺骗词法作用域有两种机制,但是这样会导致性能下降,下面我们来看看这两种机制分别是什么吧。
- eval
eval函数可以接受一个字符串参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。简而言之就是在你调用eval的地方生成代码并执行。
function foo(str, a){
eval(str); //欺骗词法
console.log(a, b);
}
var b = 2;
foo("var b=3;", 1); // 1, 3
如上例子,eval的调用会在当前的位置执行其中代码,所以会在那里执行var b=3; 那么会屏蔽掉全局作用域中的var b=2;这样输出的值是1,3。
在严格模式中,eval会有自己的作用域,这就意味着其无法修改所在的作用域。
- with
with关键字的作用是将代码的作用域设置到一个特定的对象中。
var obj={
a:1,
b:2,
c:3
}
with(obj){
a=3;
b=4;
c=5;
}
- 性能
JavaScript引擎会在编译阶段进行数项的优化。其中部分优化是根据代码的词法进行静态分析,并预先确定所有变量和函数的位置,才能在执行过程中快速找到标识符。
但是如果使用了eval和with,它只能简单的假设关于标识符位置的判断都是无效的。因为无法在词法分析阶段知道他们接受到的是什么。
网友评论