1、理解编译
在传统的编译语言中,在代码执行之前一般会经历以下三个步骤,我们统称为编译
分词/词法分析
这个过程中,会将字符串分解成有意义的代码块,这些代码块被称为词法单元
解析/语法分析
将词法单元转换成一个由元素逐级嵌套锁组成的代表了程序语法结构的树,这个树被称为抽象语法树AST
代码生成
将AST转换成可执行的代码
JavaScript大致上也遵循以上的三个步骤,但是他相对会更复杂一些,例如在词法分析和代码生成的阶段会有特定的步骤对他进行运行性能的优化,包括对冗余元素进行优化等等
2、理解作用域
- RHS引用和LHS查询
RHS和简单的查找到某个变量的值没有什么区别,而LHS则是在尝试找到变量容器的本身,从而对他进行赋值操作,
我们可以简单的将RHS理解成“取值”,将LHS理解为“赋值”
1.例子:
function foo(a){
console.log(a);
}
foo(2);
2.分析
function foo(a){ // a=2 存在一个隐式操作 对a进行LHS引用
console.log(a); // console进行RHS引用 对a进行RHS引用
}
foo(2); // RHS
3.借助对话了解整个过程
引 擎:作用域,我需要对foo进行RHS引用查询,你见过它嘛?
作用域:编译器刚刚声明了它,他是一个函数,给你。
引 擎:谢谢,让我来执行一个foo
引 擎:作用域,我需要对a进行一个LHS引用,你有见过a嘛?
作用域:编译器将他声明成了foo函数的一个形式参数,给你。
引 擎:收到,我这就把2赋值给a
引 擎:作用域,我现在需要对console进行一个RHS引用,你见过他嘛?
作用域:这个console是一个内置的对象
引 擎:在console上有一个log,是一个函数他又一个参数是a
引 擎:作用域,能帮我在对a进行一个RHS引用嘛。我需要和你确认以下a的值是否变更过
作用域:我帮你看了以下a的值依旧是2没有变过,我把a的值传给你
4.练习
function foo(a){
var b=a;
return a+b;
}
var c=foo(2);
// 从执行的顺序来看
// var c=foo(2) => 对c执行LHS引用把foo()函数赋值给c,对foo(2)执行RHS
// a=2 => 对a执行LHS引用把2的值赋值给a
// var b=a => 对b执行LHS引用把a的值赋值给b,因为a是一个变量所以在赋值操作前需要对a进行了RHS引用
// return a+b => 对a和b分别执行了RHS引用
// 综上分析,一共有3个LHS引用,4个RHS引用
- 嵌套作用域
通过对上述RHS和LHS的理解,我们可以知道作用域其实是根据名称查找变量的一个规则。但是通常我们在编写程序的时候需要顾及到多个作用域。
考虑如下代码
function foo(a){
console.log(a+b);
}
var b=2;
foo(a);
对于b的RHS引用无法在foo中完成,但是可以在上一级(在本例子中foo的上一级作用域是全局作用域)作用域中完成
借助对话了解整个过程
引 擎:foo作用域,我需要对b进行一个RHS引用,你见过它嘛?
作用域:我没有见过b,要不你去找我上级问问。
引 擎:foo的上级作用域兄弟,你见过这个b嘛,我现在需要对他进行RHS引用。
作用域:编译器在我这里声明了b,并且刚刚对他经行了LHS引用,如果你有需要的话我把它给你
嵌套作用域的规则其实很简单,引擎从当前执行的作用域开始查找变量,如果找不到,就会向上一级继续查找,最终抵达了最外层的全局作用域的时候如果还没有找到,那么整个查找过程将会停止
图:
<svg width="580" height="400" xmlns="http://www.w3.org/2000/svg"> <!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ --> <g> <title>background</title> <rect fill="#fff" id="canvas_background" height="402" width="582" y="-1" x="-1"/> <g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid"> <rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/> </g> </g> <g> <title>Layer 1</title> <rect stroke="#000" id="svg_3" height="306.000009" width="179.999995" y="43.109373" x="4" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_9" height="33" width="31" y="113.109375" x="136.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_10" height="33" width="31" y="161.109375" x="136.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_11" height="33" width="31" y="211.109375" x="136.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_12" height="33" width="31" y="263.109375" x="136.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_18" height="33" width="31" y="62.109375" x="136.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_19" height="33" width="31" y="113.109375" x="78.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_20" height="33" width="31" y="161.109375" x="78.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_21" height="33" width="31" y="211.109375" x="78.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_22" height="33" width="31" y="263.109375" x="78.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_23" height="33" width="31" y="62.109375" x="78.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_24" height="33" width="31" y="114.109375" x="19.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_25" height="33" width="31" y="162.109375" x="19.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_26" height="33" width="31" y="212.109375" x="19.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_27" height="33" width="31" y="264.109375" x="19.5" stroke-width="1.5" fill="#fff"/> <rect stroke="#000" id="svg_28" height="33" width="31" y="63.109375" x="19.5" stroke-width="1.5" fill="#fff"/> <rect id="svg_29" height="29" width="64" y="320.109375" x="62" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="#fff"/> <line stroke="#000" stroke-linecap="null" stroke-linejoin="null" id="svg_30" y2="349.109375" x2="578.999976" y1="349.109375" x1="-4" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" fill="none"/> <rect id="svg_35" height="1" width="0" y="228.109375" x="693" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <rect id="svg_37" height="0" width="1" y="261.109375" x="688" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <rect id="svg_47" height="218" width="307" y="78.109375" x="237" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <rect id="svg_48" height="1" width="0" y="-79.890625" x="675" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <line stroke-linecap="null" stroke-linejoin="null" id="svg_49" y2="134.109375" x2="236.5" y1="86.109375" x1="159.5" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <text style="cursor: move;" xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_50" y="208.109375" x="268.5" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">公共区域</text> <rect id="svg_51" height="92" width="158" y="81.109375" x="382.5" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <rect id="svg_52" height="112" width="96" y="177.109375" x="443.5" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="none"/> <text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_53" y="131.109375" x="441.5" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">主卧</text> <text xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="24" id="svg_54" y="242.109375" x="468.5" fill-opacity="null" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#000000">次卧</text> </g> </svg>
我们拿一栋楼举例,楼(全局作用域),楼有许多房子(子作用域), 房子里有卧室(孙作用域)。 在卧室里找不到,就去房子公共区域找,房子里找不到就去楼层找,依次类推,一旦到达全局作用域,不管你有没有找到你所需要的变量,整个查找过程都将停止
- 异常
之所以需要取分LHS和RHS是因为,在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询会出现不一样的结果
考虑如下代码:
function foo(a){
console.log(a+b); // Uncaught ReferenceError: b is not defined
b=a;
}
foo(2);
// 按顺序执行
// foo(2) 对foo进行RHS引用
// a=2 对a经行LHS
// console.log(a+b) 对console进行RHS引用,对变量a进行RHS引用,变量b进行RHS引用,抛出引用错误
可以看到RHS在所有嵌套作用域中遍历后找不到所需要的变量,引擎会抛出一个ReferenceError的引用错误异常。如果RHS查询到了一个变量,但是你在尝试对这个变量进行不合理的操作的时候,比如对一个非函数类型的数据进行函数操作的时候,或者引用null或undefined类型的值中的属性,那么引擎则会抛出一个TypeError的类型错误异常。
但是,当引擎在进行LHS查询的时候,如果在全局作用域中也无法查到目标变量的时候,将会在全局作用域中创建一个具有该名称的变量,并将它返回给引擎(前提是程序运行在非“严格模式”下)。用对白来理解就是,针对某个不存在的变量进行LHS查询,全局作用域的回复引擎:“不这个变量之前不存在,但是我很热心的帮你创建了一个”
3、总结
作用域其实就是一套用域确定在何处以及如何查找变量的规则,如果查找的目的是对变量进行赋值,那么就会使用LHS查询;如果目的是为了获取变量的值,就会使用RHS查询。
赋值操作会导致LHS查询,=操作符或者调用函数时传入参数的操作,都会导致关联的作用域的赋值操作
JavaScript引擎会在代码执行之前对代码进行编译,像var a = 2 这样的声明会被分解成两个步骤
首先,var a 在其作用域中声明新的变量,这会在最开始的阶段进行,也就是代码执行之前,我们称之为变量提升。
接下来,a = 2 会查询(LHS查询)变量a并对其进行赋值。
LHS和RHS查询都是从当前作用域开始,逐级向上查询,最终到全局作用域结束
不成功的RHS查询会抛出ReferenceError异常,不成功的LHS查询会隐式的创建一个全局变量(非严格模式下)