很多时候,我们不理解js一些代码的输出,其实是因为我们不理解js编译器的原理,在掌握了编译器的运行过程,会发现,原来那些看起来很难的问题,突然变得好简单。
前些天,师妹给了我一段代码,让我帮她解析一下。代码如下
![b9a5820f6d11f50eac1cce665b19d752.png](https://i-blog.csdnimg.cn/blog_migrate/150cf3141a28bb4b07441250b7126add.png)
我的第一反应是输出了10(或许很多人与我有一样的答案)。师妹说,输出了undefined。她让我解释一下,为什么输出了undefined。当时的我并没有很在意这个问题,直到我无意中在书上看到了关于编译器原理的研究。
编译器在遇到var a的时候,会询问作用域是否已经有一个该名称的变量存在于同一个作用域,如果有,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。
由于fun()方法里面定义了一个num变量,编译器遇到var num的时候,在当前作用域中声明了一个新的变量num,由于在输出num的时候,变量num还没被赋值,因此输出了undefined
我开始对编译器编译产生了兴趣。
再来看下面一段代码
![e0d53a06b7545c69b619af2a6e525716.png](https://i-blog.csdnimg.cn/blog_migrate/be1b1c01b74d57ea921bbddc29ac9512.png)
这题会提示错误?还是输出undefined?还是。。。。
这个考的也是编译器原理,不过,这个要先了解一下RHS和LHS
什么是LHS和RHS(看起来好吓人)
LHS和RHS的含义是赋值操作的左侧或右侧(注:这里的赋值并不只是指 等号赋值)。
RHS和LHS的区别表现在RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图找到变量的容器本身,从而可以对其赋值。如以下代码:
console.log( a );
其中对 a 的引用是一个 RHS 引用,因为这里 a 并没有赋予任何值。相应地,需要查找并取得 a 的值,这样才能将值传递给 console.log(..)。
相比之下,例如:a = 2;这里对 a 的引用则是 LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为 = 2 这个赋值操作找到一个目标。
在变量还没声明的时候,RHS和LHS都会先在当前作用域下查找该变量,如果未找到,则会去全局作用于寻找该变量。
不同的是, RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError异常。
而当引擎执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下。
![e0d53a06b7545c69b619af2a6e525716.png](https://i-blog.csdnimg.cn/blog_migrate/be1b1c01b74d57ea921bbddc29ac9512.png)
这段代码中,num = 20为LHS赋值,LHS在嵌套作用域中都没找到num变量,所以会在全局作用域中创建一个num变量,所以上面的代码输出了20而不是报错。
看了一下自己写的东西,发现关于LSH与RSH的说明不是很清楚,对编译过程写的不是很清晰,做一下补充。
首先是如何快速区分LSH与RSH。有一个很简单的方法,就是如果是要对变量进行赋值,就是LSH,如果是查询变量的值,那就是RSH.
关于js的编译。
JS编译分三个步骤,词法分析、语法分析以及代码生成。
编译过程涉及三个角色,引擎、编译器和作用域。
引擎是贯穿整个编译过程的,相当于主干。而编译器,主要是负责词法分析、语法分析与代码生成。作用域主要负责收集与维护标识符集合(应该就是我们声明的变量),并且控制当前代码对标识符的访问权限。先来看看词法分析、语法分析、代码生成的过程。
举个例子: 对于var a = 2;
词法分析: 编译器会把var a = 2;这个字符串拆分成词法单元,也就是var、a、=、2、;,词法生成器判断a是一个独立的词法单元还是其他词法单元的一部分,如果用的是有状态的解析规则,这个过程就是词法分析。
语法解析:将词法单元流(也就是[var,a,=,2,;])转换成 由元素逐级嵌套的 代表了 程序语法结构的树,也就是抽象语法树(Abstract Syntax Tree),简称AST,这个过程是语法解析。
代码生成: 将AST转换成一组机器指令,用来创建一个叫做a的变量,包括分配内存等,并存储一个值在a中
整个流程: 首先,当编译器遇到var a的时候,编译器会去询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明
接下来编译器会为引擎生成运行时所需要的代码,这些代码被用来处理a=2这个赋值操作。引擎会先询问作用域,在当前作用域集是否存在a这个变量,如果是,引擎会直接使用这个变量,如果不是,就继续寻找