JavaScript学习笔记1--作用域

  |  

前言

这部分主要学习JavaScript作用域和闭包,作用域是任何一个语言中都很重要的部分


11



编译原理

传统编译语言的源代码的编译过程:

  • 分词/词法分析(Tokenizing/Lexing):

    这个过程将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元(token)。空格是否会被当成词法单元,取决于空格在这门语言中是否具有具体意义。

  • 解析/语法分析(Parsing):

    这个过程是将词法单元流(数组)装换成要给由元素逐级嵌套所组成的代表了程序语法结构的树,这个数被成为“抽象语法树”(Abstract Syntax Tree, AST)

  • 代码生成:

    将AST转换为可执行代码的过程被成为代码生成。这个过程与语言、目标平台等息息相关。

分词和词法分析之间的区别:

​ 主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。简单来说,如果词法单元生成器在判断一个变量是一个独立的词法单元还是其它词法单元的一部分时,调用的是有状态的解析规则,那么这个过程被称为词法分析

理解作用域

参与作用域的几个重要部分

  • 引擎:从头到尾负责整个JavaScript程序的编译及执行过程
  • 编译器:引擎的好朋友之一,负责语法分析及代码生成等脏活累活
  • 作用域:引擎的另一个位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限

对话

通过var a = 2;这个例子分析,来简单的看一下引擎和其它部分是如何协同工作的

  • 首先,遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则他会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a。
  • 记下来编译器会为引擎生成运行时所需的代码,这些代码并用来处理a = 2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫做a的变量。如果是,引擎会使用这个变量。如果否,引擎会继续查找该变量。
  • 如果引擎最终找到了a变量,就会将2赋值给它。否则引擎就会抛出一个未定义的异常

总结: 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

引擎的查询类型

引擎查找变量的方式分为LHS和RHS:

  • LHS:查找赋值操作的目标是谁,举例a = 2,其中查询变量a的方式就是LHS
  • RHS:查找谁是赋值操作的源头,举例console.log(a),这里对于变量a的查询方式就是RHS,它在于找到a的原值

由于编译器可以在代码生成的同时处理声明和值的定义。所以并不能将function foo(a){}概念化为var foo;foo = function(a){}这样的普通的变量声明和赋值。所以将函数声明理解为LHS查询和赋值的形式并不合适

引擎和作用域的对话

示例代码:

1
2
3
4
function foo(a) {
console.log(a); //2
}
foo(2);

下面分析一下这部分代码在执行时引擎和作用域之间到底进行了哪些对话:

  • 引擎在执行foo(2)时,会对foo进行RHS查询。
  • 作用域会在自身进行查询,然后会发现编译器在之前声明了foo这个函数
  • 引擎会执行foo,这里有一个隐式的对a的LHS引用,就是对于2的赋值
  • 作用域会在自身进行查询,然后会发现编译器在之前将a声明为了foo的一个形参
  • 引擎对a进行赋值操作,并对console进行RHS引用
  • 作用域查询发现console是个内置对象
  • 引擎再一次对a进行RHS查询
  • 作用域检测变量a
  • 引擎将a传递给log()

这就是引擎与作用域交互的整个过程

测试

分析这部分代码中的所有LHS查询和RHS查询:

1
2
3
4
5
6
function foo(a) {
var b = a;
return a + b;
}

var c = foo(2);

LHS查询:

  • var c = foo(2)中对变量c的LHS查询
  • var c = foo(2)中对与形参a的隐式LHS查询
  • var b = a中对变量b的LHS查询

RHS查询:

  • var c = foo(2)中对foo()函数的RHS查询
  • var b = a中对变量a的RHS查询
  • return a + b中对变量a和变量b的两次RHS查询

作用域嵌套

作用域是根据名称查找变量的一套规则。当一个块或函数嵌套在另一个块或函数中时,就会发生作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,知道找到该变量,或直到最外层作用域(全局作用域)为止。

异常

区分LHS和RHS是因为在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询的行为是不一样的:

  • 如果RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常
  • 在引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,在非“严格模式”下,全局作用域中就会创建一个具有该名称的变量,并将其返回给引擎。而在ES5及以后的严格模式下,LHS查询失败同样会抛出ReferenceError异常

当RHS查询成功后,如果尝试对这个变量的值进行不合理的操作,就会抛出TypeError异常

文章目录
  1. 1. 编译原理
  2. 2. 理解作用域
    1. 2.1. 参与作用域的几个重要部分
    2. 2.2. 对话
    3. 2.3. 引擎的查询类型
    4. 2.4. 引擎和作用域的对话
    5. 2.5. 测试
  3. 3. 作用域嵌套
  4. 4. 异常