该系列是《你不知道的JavaScript(上)》的读书笔记。记录成文字,加深学习印象。

一、JavaScript编译原理

传统的语言编译一般经历三个过程:

  • 分词/词法分析
  • 解析/语法分析
  • 代码生成
    而JavaScript引擎则要复杂得多了。简单来说就是任何JavaScript代码在执行前都要进行编译(通常在代码执行前)。

    二、理解作用域

    要理解作用域之前需要了解下什么是作用域,它有什么作用?同时还需要了解JavaScript引擎和编译器是什么?
  • 引擎
    从头到尾负责整个JavaScript程序的编译及执行过程。
  • 编译器
    负责语法分析及代码生成
  • 作用域
    负责收集并维护由所有声明的标识符(及变量)组成的一系列查询,并实施一套规则,确定当前执行的代码对这些标识符的访问权限(简而言之就是规定了谁有权限访问哪些变量)。
    现在以var a = 2这个简单的变量命名过程来分析,JavaScript引擎会将其看做两步var a;a = 2两步进行。详见下面的流程图
    JavaScript变量编译流程图
    总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域查找该变量,如果能够找到便对它进行赋值。

    关于变量查找的类型

    编译器在编译过程,会对变量进行查询。一种是LHS查询,另外一种查找的类型是RHS。
  • LHS 当变量出现在赋值操作的左侧时进行LHS查询 (赋值操作的目标是谁)
  • RHS 当变量出现在赋值操作的非左侧时,进行RHS查询(谁是赋值操作的源头,取到这个变量的源值)
    试着找出下面的例子各有多少个LHS和RHS
    1
    2
    3
    4
    5
    function foo(a){
    var b = a;
    return b+a;
    }
    var c = foo(2);

答案在结尾

三、作用域嵌套

所谓作用域嵌套就是当一个块或函数嵌套在另外一个块或函数中,就发生了作用域的嵌套。因此在当前作用域无法找到该变量时,就会往外层嵌套作用域中继续寻找,直到找到该变量或者抵达最外层的作用域(也就是全局作用域)为止。

四、异常

前面提到的LHS和RHS两种查询,如何区分它们是非常重要的一件事。
因为在变量尚未声明之前,二者的查询行为是不一样的。如下面例子

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

想一下,输出的值应该是什么?
答案:Uncaught ReferenceError: b is not defined(…),结果会报错。因为b并没有被定义,因此引擎就抛出ReferenceError异常。
为什么会导致这样的结果呢?这是因为在对变量b进行RHS查询的时候,如果在作用域中没有找到该变量,也就是说明这是一个“未声明”的变量,这时候引擎就会抛出ReferenceError异常。
相比较之下,如果是对变量b进行LHS查询的时候,如果在全局作用域也没有找到该变量的话,全局作用域便会自动创建该变量,前提是在非严格模式下。这就是LHS和RHS的两种查询类型的区别
同样,在JavaScript中,也有两种异常类型。一种就是刚刚说到的ReferenceError,另外一种则是TypeError。那这二者有什么区别吗?

  • ReferenceError指的是同作用域判别失败相关,简单说就是在作用域找不到该变量。
  • TypeError指的是作用域判别成功,但对结果的操作是非法或者不合理的。简单说就是在作用域找到该变量,但是该变量的值不符合。

前面问题的答案
LHS

  • c=
  • a=2
  • b=
    RHS
  • foo(2)
  • =a
  • a(return的时候要去查找a的值)
  • b(return的时候要去查找b的值)