JS是“动态”、“解释执行”的语言,而事实上她是一门编译语言
与传统便以语言不同,她不是提前编译的
在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
编译过程:
- 分词/词法分析:将代码字符串分解成有意义的 代码块——词法单元(token)
eg. var a = 0;
分解成 var
、a
、=
、2
、;
。 空格是否被当做词法单元取决于空格在这门语言中是否具有意义。
分词(tokenizing) VS 词法分析(Lexing)
二者区别在于:词法单元的识别是通过 有状态 还是 无状态 的方式进行的
词法单元生成器在判断 a
是一个独立的词法单元还是其他词法单元的一部分是,调用的是有状态的解析规则还是无状态的解析规则,有状态的是词法分析,无状态的是分词
- 解析/语法分析(Parsing)
将 词法单元流(数组) 转换为 抽象语法树(Abstract Syntax Tree, AST :由元素逐级嵌套所组成的代表了程序语法结构的树)
eg.
var a = 0;
可能解析成如下ASTVariableDeclaration //声明变量 |_Identifier //标识符 |_AssignmentExpression //赋值表达式 |_NumericLiteral //数值文字 2
- 代码生成 将AST转换为可执行代码,此过程与语言、目标平台等息息相关
比起那些编译过程只有三个步骤的语言的编译器,JavaScript 引擎要复杂得多。例如,在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。
JavaScript 引擎不会有大量的(像其他语言编译器那么多的)时间用来进行优化,因 为与其他语言不同,JavaScript 的编译过程不是发生在构建之前的。 对于 JavaScript 来说,大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时 间内。在我们所要讨论的作用域背后,JavaScript 引擎用尽了各种办法(比如 JIT,可以延 迟编译甚至实施重编译)来保证性能最佳。 简单地说,任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。因此, JavaScript 编译器首先会对 var a = 2; 这段程序进行编译,然后做好执行它的准备,并且 通常马上就会执行它。
当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。 LHS 和 RHS 的含义是“赋值操作的左侧或右侧”并不一定意味着就是“ = 赋值操作符的左侧或右侧”。赋值操作还有其他几种形式,因此在概念上最 好将其理解为“赋值操作的目标是谁(LHS)”以及“谁是赋值操作的源头 (RHS)”。