词法作用域
作用域是一套规则:用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量查找。
作用域的两种工作模型:
- 词法作用域
- 动态作用域(Bash脚本、Perl中的一些模式)
1. 词法阶段
词法化对源代码中的字符进行检查,词法作用域是定义在词法阶段的作用域。
在查找时,作用域气泡的结构和相互之间的位置关系给引擎提供了足够的位置信息,引擎通过这些信息来查找标识符的位置。
遮蔽效应
作用域查找会在的一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符。
全局变量会自动成为全局对象(比如浏览器中的window 对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引用来对其进行访问。
2. 欺骗词法
一般来说,词法作用域完全由写代码期间所声明的位置来定义,欺骗词法则是在运行时“修改”/“欺骗”词法作用域。
欺骗方式
- eval:执行动态创建的代码;严格模式下,其中的声明无法修改所在的作用域
- with:通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
with的说明:
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!
当o2传进去时,o2没有a属性,也就不会创建这个属性o2.a = undefined;
就会通过LHS查找到全局作用域,并创建一个全局变量a
结论
- eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域
- with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域
导致的问题:性能的下降
JavaScript引擎在编译阶段会进行数项的性能优化,有些优化是依赖于能够根据代码的词法进行静态分析,预先确定所有变量和函数定义的位置,才能在执行时快速找到
如果代码中大量使用eval(..) 或with,那么运行起来一定会变得非常慢。无论引擎多聪明,试图将这些悲观情况的副作用限制在最小范围内,也无法避免如果没有这些优化,代码会运行得更慢这个事实
3. 小结
词法作用域意味着作用域是由写代码时函数声明的位置来决定的,所以编译的词法分析阶段基本知道全部标识符在哪里,以及如何声明。