几乎全部的编程语言都可以储存变量,而且能在以后对这个变量值进行访问或修改,正是储存和访问变量的能力将状态带给了程序,那么,这些变量储存在哪里呢?程序须要时又是如何找到他们?这些问题说明须要一套设计良好的规则来储存变量,而且以后能够方便的找到这些变量,这套规则被称为做用域。编程
尽管将JS归类为“动态”或“解释执行”脚本语言,但事实上它是一门编译语言。可是与传统编译语言不一样的是,它不是提早编译的,编译结果也不能在分布式系统中进行移植。JS引擎进行编译的步骤与传统的语言很是类似,程序中一段源代码在执行以前会经历三个步骤,统称为“编译”。数组
这个过程会将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元。例如,考虑程序
var a = 2;
。这段程序一般会被分解成 为下面这些词法单元:var、a、=、2 、;
。编程语言
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的表明了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。
var a = 2;
的抽象语法树中可能会有一个叫做VariableDeclaration的顶级节点,接下来是一个叫做 Identifier(它的值是 a)的子节点,以及一个叫做 AssignmentExpression 的子节点。AssignmentExpression 节点有一个叫做 NumericLiteral(它的值是 2)的子节点。分布式
AST转换为可执行代码的过程称被称为代码生成,简单来讲就是有某种方法能够将
var a = 2;
的AST转化为一组机器指 令,用来建立一个叫做a的变量(包括分配内存等),并将一个值储存在a中。函数
编译流程以下图所示:性能
JS引擎比传统的编译语言编译器复杂不少,在语法分析和代码生成阶段有特定的步骤来对性能进行优化,大部分状况下编译发生在代码以前的前几微秒,在讨论做用域背后,js引擎用了各类办法来保证性能最佳。优化
Tips:咱们平时在写JS代码的时候,一个语句结尾要加分号(;),便于JS编译器编译。设计
咱们先了解JS编译过程当中几个名词,JS引擎,编译器,做用域。code
2.1.名词介绍blog
2.2.变量赋值
对于var a=2;
这段代码,咱们认为这就是申明一个为变量a且初始值为2,实际上,JS引擎认为这里有两个彻底不一样的申明,一个由编译器在编译时处理,另外一个则由引擎在运行时处理。
处理过程分为两步:
1.遇到var a
,编译器会询问做用域是否已经有一个该名称的变量存在于同一个做用域的集合中。若是是,编译器会忽略该声明,继续进行编译;不然它会要求做用域在当前做用域的集合中声明一个新的变量,并命名为a。
2.接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2
这个赋值操做。引擎运行时会首先询问做用域,在当前的做用域集合中是否存在一个叫做a的变量。若是是,引擎就会使用这个变量;若是否,引擎会继续查找该变量。
若是引擎最终找到a变量,就会将2赋值给它。不然就抛出异常。
Tips:声明提早(hoist)-JS引擎在建立变量时,会将该变量提高到当前做用域的最前面。
总结:变量的赋值操做会执行两个动做,首先编译器会在当前做用域中声明一个变量(若是以前没有声明过),而后在运行时引擎会在做用域中查找该变量,若是可以找到就会对它赋值。
2.3.LHS查询&RHS查询
编译器在编译过程当中的第二步生成了代码,引擎在执行时,会经过查找变量a来判断它是否已经声明过。当变量出如今赋值操做的左侧时进行LHS查询,当变量出如今右侧时进行RHS查询。
console.log(a); //对a的引用时RHS引用,这里没有对a赋予任何值,须要查找a的值。 a=2; //对a的引用是LHS引用,由于这里不关心a的值等于多少,只想为 =2 这个赋值操做找到一个目标(变量a);
LHS和RHS的含义是“赋值操做的左侧或右侧”并不必定意味着就是“= 赋值操做符的左侧或右侧”。赋值操做还有其余几种形式,所以在概念上最好将其理解为“赋值操做的目标是谁(LHS)”以及“去找到XX变量的值,谁是赋值操做的源头(RHS)”。
做用域是根据名称查找变量的一套规则。实际状况中,一般须要同时顾及几个做用域。 当一个块或函数嵌套在另外一个块或函数中时,就发生了做用域的嵌套。所以,在当前做用 域中没法找到某个变量时,引擎就会在外层嵌套的做用域中继续查找,直到找到该变量, 或抵达最外层的做用域(也就是全局做用域)为止。
参考如下代码:
var name='peer'; function sayHello(){ alert('hello '+ name) } sayHello(); // 对name的RHS引用没法在函数sayHello完成,可是能够在上一级做用域中完成。
把做用域比喻成一个建筑以下图所示:
LHS和RHS引用都会在当前楼层进行查找,若是没有找到,就会坐电梯前往上一层楼,若是仍是没有找到就继续向上,以此类推。一旦抵达顶层(全局做用域),可能找到了你所需的变量,也可能没找到,但不管如何查找过程都将中止。
做用域是一套规则,用于肯定在何处以及如何查找变量(标识符)。LHS和RHS查询都会在当前执行做用域中开始,若是有须要就会向上级做用域继续查找目标标识符,这样每次上升一级做用域,最后抵达全局做用域,不管找到或没找到都将中止。不成功的RHS 引用会致使抛出ReferenceError异常。不成功的LHS引用会致使自动隐式地建立一个全局变量(非严格模式下),掌握这些基本做用域知识能使咱们更深刻理解JS引擎的编译过程来编写更高性能的代码。
参考资料: 《你不知道的JavaScript》