在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉。
本期我给大家讲述的是 前端经典js书籍 <<你不知道的javaScript(上卷)>> 第一章内容的知识点总结和讲解。
1.1 编译原理
尽管通常将js归类为“动态”或“解释执行”语言,但事实上它是一门编译语言。但与传统的编译语言不同,他不是提前编译的,编译结果也不能在分布式系统中进行移植。在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
1>分词/词法分析
这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元。例如,考虑程序 var a=2;这段程序通常会被分解成为下面这些词法单元 : var 、a、=、2、;。空格是否会被当做词法单元,取决于空格是否在这门语言中具有意义。
2>解析/语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树称为“抽象语法树”(AST)。
3>代码生成
将AST转换为可执行代码的过程被称为代码生成。这个过程与语言、目标平台等息息相关。抛开具体细节,简单的来说就是有某种方法可以将 var a=2 ;的AST转化为一组机器指令,用来创建一个叫做a的变量(包括分配内存等),并将一个值存储在a中。
1.2理解作用域
为了进一步理解,我们需要多介绍一点编译器的术语。在我们的例子中,引擎会为变量a进行LHS查询。另外一个查找的类型叫做RHS查询。我打赌你一定能猜到“L”和“R”的涵义,它们分别代表左侧和右侧。什么东西的左侧和右侧?是一个赋值操作的左侧和右侧。
换句话说,当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。讲得更准确一点,RHS查询与简单地查找某个变量的值别无二致,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值。从这个角度来说,RHS并不是真正意义上的“赋值操作的右侧”,更准确的说是“非左侧”。
考虑以下代码:
1 console.log(a); 其中对a 的引用是一个RHS引用,因为这里a并没有赋予任何值。相应地,需要查找并取得a的值,这样才能将值传递给console.log(...)。
相比之下,例如:
1 a=2; 这里对a的引用则是一个LHS引用,因为实际上我们并不关心当前的值是什么,只是想要为=2这个值赋值操作找到一个目标。
1.3作用域嵌套
当一个块或函数嵌套在另一个块或函数中,就发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域为止)。
考虑以下代码:
1 function foo(a){
2 console.log( a+b );
3 }
4 var b=2;
5 foo( 2 ); //4
对b进行的RHS引用无法在函数foo内部完成,但可以在上一级作用域(在这个例子中就是全局作用域)中完成。
把作用域链比喻成一个建筑:第一层楼代表当前的执行作用域,也就是你所处的位置。建筑的顶层代表全局作用域。LHS和RHS引用都会在当前楼层进行查找,如果没有找到,就会乘坐电梯前往上一层楼,如果还没找到就继续向上,以此类推。一旦抵达顶层(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。
1.4异常
为什么区分LHS和RHS是一件重要的事情? 因为在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询的行为是不一样的。
考虑如下代码:
1 function foo(a){
2 console.log( a+b );
3 b=a;
4 }
5 foo( 2 );
第一次对b进行RHS查询时是无法找到该变量的。也就是说,这是一个“未声明”的变量,因为在任何相关的作用域中都无法找到它。
如果RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError异常。相较之下,当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非严格模式下。
严格模式下在行为上有很多不同。其中一个行为就是禁止自动或隐式地创建全局变量。因此,在严格模式下LHS查询失败时,并不会创建并返回一个全局变量,引擎会抛出同RHS查询失败时类似的 ReferenceError异常。
小结:作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用 LHS查询;如果目的是获取变量的值,就会使用 RHS查询。赋值操作符会导致 LHS查询。=操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。js引擎首先会在代码执行前对其进行编译,在这个过程中,像 var a=2 ;这样的声明会被分解成两个独立的步骤:
1.首先,var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
2.接下来,a=2 会查询(LHS查询) 变量a并对其进行赋值。
LHS和RHS查询都会在当前执行作用域开始,如果有需要(也就是说它们没有找到所需的标识符),就会向上级作用域继续查找目标标识符,这样每次上升一级作用域(一层楼),最后抵达全局作用域(顶层),无论找到还是没找到都将停止。
不成功的RHS引用会导致抛出 ReferenceError异常,不成功的LHS引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用LHS引用的目标作为标识符,或者抛出 ReferenceError 异常(严格模式下)。