理解作用域

JS作用域的问题是老生常谈的问题了,我们都知道JS是不存在块级作用域的(ES6以前,try…catch是一个例外),可是仍然实际编程过程中由于对作用域的理解不够深刻,踩坑无数,苦不堪言,恰逢寒假,又到了充(wan)电(shuai)的时间,所以把学习的重点放在认识JS语言本身上面。参考了资料《你不知道的JavaScript》。

实例为先

首先,我们以var a = 1;这一个语句为例,看看这个过程JS到底干了什么。

毫无疑问JS是一门编译型的语言,但它的编译不是提前编译的,编译过程一般发生在语句执行前几个微秒。并且其和其它典型的编译语言一样,编译的步骤是相似的。首先编译器会把语句进行分词,分成单个的代码块,这些代码块被称为词法单元;接下来编译器会把词法单元流转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这棵树被称为”抽象语法树AST”;最后一步,代码生成,该过程会把AST转换成可执行代码(机器指令)。

当然,JS的编译过程不会如此简单,其也会包含性能优化等。但这些所有的工作都在执行前几个微秒的时间内完成,并且立即执行它。

在正式的开始了解作用域之前,我们首先认识一下代码在执行时所需要的工具。

  1. 引擎:负责代码编译到执行过程
  2. 编译器:负责词法分析和可执行代码的生成等
  3. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限

现在开始对var a = 1的执行进行分解:

首先编译器会把这段程序分成词法单元,并且构建AST,当遇到var a的时候,首选编译器会询问作用域,是否已经已经有一个同名的变量存在于这个作用域集合中,如果存在则忽略该语句,否则在该作用域申明一个变量,命名为a;接下来,编译器会为引擎生成可执行代码,用于处理a=1这个赋值操作,引擎会询问当前的作用域是否存在一个名字为a的变量,如果存在,则使用这个变量,否则继续操作。最终,如果找到变量a,则给其赋值为1,否则将抛出错误。

总结:在对变量赋值时会存在两个步骤:首先编译器会在作用域中生成一个变量;然后引擎在运行时查找该变量,如果存在就赋值,否则抛出错误。

作用域

1.嵌套作用域:作用域嵌套这个概念是很好理解的,当一个块嵌套在另一个块或函数中的时候,就会发生作用域的嵌套。当在当前作用域无法找到某个变量的时候,引擎就会在外层的嵌套作用域中寻找,知道抵达到最外层作用域或者找到了该变量为止。

考虑以下代码:

1
2
3
4
5
var a = 1;
function add(b){
console.log(a + b)
}
add(2) // 3

对变量a的查找是无法在add函数内部完成的,所以引擎会向上层作用域中查找,并且在外层作用域中找到。

LHS && RHS

引擎查找变量时常用的两种查找方式,这里需要注意的,如果执行右侧查找,无法找到该变量则会抛出引用错误。而进行左侧查找的时候,如果不成功,则会自动的隐式创建一个全局变量(都是在非严格模式下)

小结

简单来说,作用域就是一系列的规则,这套规则用于管理引擎如何在当前作用域以及嵌套的子域中根据标识(zhi)符名称进行变量的查找。

下一篇,将深入的学习词法作用域

分享到 评论