前言
几乎所有的编程语言最基本的功能之一,就是能够存储变量中的值,并且能在之后对这个值进行访问和修改,正是这种储存和访问变量的能力将状态带给了程序,因为需要一套设计良好的规则来储存变量,而这套规则被称为作用域。
正文
1.JS和传统编译语言的区别
通常将JavaScript归类为“动态”或“解释执行”语言但事实上他是一门编程语言,与和传统编译语言的不同:
- 不能提前编译
- 编译结果不能在分布系统中进行移植
- JS引擎进行编译的步骤和传统编译语言相似,但更复杂
- 因为JS引擎在代称生成的时候,会进行代码优化和降低冗余代码的生成
1.1.传统编译语言流程
分词/词法分析—>解析/语法分析—>代码生成
1.分词/词法分析
含义:将字符组成的字符串分解成有意义的代码块。(词法单元)。例如,var a=2;---->var、a、2;空格是否被当做词法单元,取决于在这门语言中是否具有意义。
1.2词法分析
词法分析:通过词法单元生成器来判断一个字符是否为一个独立的词法单元,其间调用了有状态的解析规则,这个过程就是词法分析。这个过程生成了词法单元流(一般体现为数组)。
分词和词法分析的区别:
两者主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。
博主个人感悟:分词是进行词法分析的上一步操作,JS引擎在简单的将字符串分解成无状态的词法单元后,紧接着就会进行词法分析,用词法单元生成器调用有状态的解析规则,来进一步判断其中每一个字符是否为一个独立的词法单元,这个过程就是词法分析,并且生成了词法单元流(数组),
2.解析/语法分析
含义:将词法分析生成的词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树就是抽象语法树。(AST)
3.代码生成
作用:将抽象语法树(AST)转化成可执行代码。
以上三个步骤是传统编译方式,事实上,JS引擎要复杂的多,在语法分析和代码生成阶段有特定的步骤会对运行性能进行优化,包括对冗余代码进行优化。(JS引擎不会有大量的时间来进行优化,他的编译发生在代码执行前的几微秒),在我们所要讨论的作用域背后,JS引擎用尽了各种办法(比如JIT,可以延时编译甚至是实施重编译。),来保证性能最佳。
2.理解作用域
2.1.1成员表
在理解作用域之前,我们先了解一下对var a=2,处理过程的成员。
- 引擎
负责整个JS程序的编译和执行 - 编译器
引擎最好的朋友之一,负责语法分析和代码生成。 - 作用域
引擎的另外一个好朋友,负责收集并维护所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
2.1.2作用域的概念:
负责收集和维护所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
(规范一个标识符的访问权限/规定一个标识符的使用范围)
2.成员表:参与对一个字符串处理的过程有三个成员,引擎(负责JS程序的编译和执行)、编译器(负责词法语法分析和代码生成)和作用域
3.作用域嵌套的概念
一个块或函数嵌套在另一个块或函数中时,就会发生作用域嵌套。
遍历嵌套作用域的规则:js引擎从当前的执行作用域开始查找变量,逐级查找,一直到最外层,至找到为止。
4.LHS和RHS的区别
RHS:指的是赋值操作的源头是谁
LHS:指的是赋值操作的目标是谁,试图找到变量容器的本身,从而对其赋值。
5.为什么要区别LHS和RHS
因为在JS引擎在相关作用域查询变量的时候,如果RHS查询找不到所需的变量,就会抛出ReferenceError异常,而用LHS查询则会自动一个具有该名称的变量,但前提是程序运行在非严格模式下。
严格模式下,两者查询方法都会抛出ReferenceError异常。
疑问1:
既然有两种查找变量的方式,那在实际写代码的过程中,如何去实际体现两者的区别,或者说应该用什么代码指令来运行程序才能体现出两者的差别?查找的目的是什么?
看了浩哥的小结明白了一点,如果单纯是为了获取变量的值就用RHS查询,如果是对某个变量进行赋值,就用LHS查询,哪怕变量不存在,在非严格模式下,也可以自动创建,不会影响程序的运行。一个程序运行的开始,两种查询变量的方法(LHS和RHS)就会触发,然后一层层的往外查找,直至查询完全局的范围,如果有异常,便会抛出。