JavaScript学习笔记2--词法作用域

  |  

前言

这部分继续《你不知道的JavaScript》的第二章,词法作用域


12



词法阶段

词法作用域就是定义在词法阶段的作用域,换句话说,词法作用域是由编写代码时将变量和块作用域写在哪里决定的,因此当词法分析其处理代码时会保持作用域不变

作用域气泡的结构和相互之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置。

引擎会从当前引用变量的作用域开始查找,逐级向上,直到找到第一个匹配的标识符或到全局作用域,但是不会向下查找。

遮蔽效应:底层作用域内部的标识符会遮蔽顶层的相同的标识符

无论函数在哪里被调用,也无论它如何被调用,他的词法作用域都只由函数被声明是所处的位置决定

词法作用域查找只会查找以及标识符,对于foo.bar.baz这样的引用,词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则会分别接管对bar和baz属性的访问

欺骗词法

欺骗词法作用域会导致性能下降

eval

eval函数可以接收一个字符串为参数,并将其中的内容视为代码执行。

在执行eval(….)之后的代码时,引擎并不知道或在意前面的代码是一动态形式插入进来的,并对词法作用域的环境进行修改的。引擎只会如往常地进行词法作用域查找。

默认情况下,如果eval(…)中所执行的代码包含有一个或多个声明(无论是变量还是函数),就会对eval(…)所处的词法作用域进行修改。

在严格模式下,eval(…)在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域。

在程序中动态生成代码的使用场景非常罕见,因为它所带来的好处无法抵消性能上的损失。

with

with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身

示例:

1
2
3
4
5
6
7
8
9
10
11
12
function foo(obj){
with(obj){
a = 2;
}
}
var o1 = { a : 3 };
var o2 = { b : 4 };
foo(o1);
console.log(o1.a); //2
foo(o2);
console.log(o2.a); //undefined
console.log(a); //2,有一个定义到了全局作用域的a

分析:

with可以将一个没有或有多个属性的对象处理为要给完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符

可以这样理解,当我们传递o1给with时,with所声明的作用域是o1,而这个作用域中含有要给同o1.a属性相符的标识符。但当我们将o2作为作用域时,其中并没有a标识符,因此进行正常的LHS标识符查找。但是整个作用域链中都没有找到标识符a,因此当a=2执行时,自动创建了一个全局变量a

性能

JavaScript引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。

但是如果引擎在代码中发现eval(…)或with,她只能简单地假设关于标识符位置的判断都是无效的,所有的优化可能都是无意义的,所以如果代码中大量使用eval(…)或with,那么运行起来将很慢。

文章目录
  1. 1. 词法阶段
  2. 2. 欺骗词法
    1. 2.1. eval
    2. 2.2. with
    3. 2.3. 性能