作用域是什么?
一,理解作用域
任何javascript代码片段在执行前都要进行编译,先看一个例子吧:
// 先思考一下这行代码是如何被编译的?
var a = 33
// 估计大部分人看到这行代码,会说首先声明一个变量a,然后给它赋值33
// 但实际上并没有这么简单
首先要先知道js在解析var a = 33这行代码的时候都有哪些东西参与了。
- 引擎:从头到尾负责整个js程序的编译及执行过程
- 编译器:负责语法分析及代码生成
- 作用域:简单点说就是存储变量的仓库,引擎和编译器通过(LHS,RHS)查询,来查找变量是否存在。暂时可以将LHS和RHS理解为一种查询方式,下边会详细说。
基于上文,事实上编译器会这么处理,继续拿var a = 33来举例子:
var a = 33
/**
* 1. 编译器会去逐行进行语法分析和代码生成,所以它首先会遇到var a
* 2. 编译器去作用域中查询(LHS),变量a是否存在,如果存在,就继续往下编译,
* 否则就会在作用域中新建一个变量a
* 3. 然后编译器会生成a = 33这行代码并交给引擎去执行,这时候引擎会向作用域发起查询(LHS),
* 询问当前作用域中是否存在变量a,如果不存在,就会向当前作用域的上一级作用域去查找
* (可以理解为当前作用域仓库外边套了一个更大的仓库,在当前仓库找不到,就跑到仓库外边那个更大的仓库去找),也叫作用域链。
* 如果在这条作用域链中还是没找到,那么就会在顶级(最最外边那个仓库)作用域下创建一个a变量,并给它赋值33
* 如果在当前作用域找到了,就会直接将33赋值给a
*/
二.LHS和RHS查询
上文中多次提到LHS和RHS查询,那么它俩到底是啥呢?
简单点说,当变量出现在赋值操作的左侧时是LHS查询,非左侧时是RHS查询。也可以将LHS理解成是给谁赋值,RHS理解为赋值操作的源头。
// 举个例子吧
console.log(a)
/**
*这里的a就是一个RHS查询,因为a没有发生任何赋值操作,所以肯定不在赋值操作的左侧,那么就是非左侧,所以它是一个RHS查询。
*然后还有一个console,同上,所以它也是一个RHS查询,回去作用域链中查找console这个对象,查到之后就会调用它的log方法去打印出a
*/
然后再深入思考下,如果通过RHS查询,并且在作用域链中没有找到变量a呢?这时候引擎就会抛出一个ReferenceError错误,表示RHS查询失败,那么如果LHS查询失败呢?
大家先来看看俩例子吧,看完这俩例子可能会对作用域以及作用域链和LHS,RHS查询有更深刻的理解:
// 先来看看下边这个例子
function fn(a) {
b = a;
}
fn(3);
console.log(b);
/**
*毫无疑问,它打印的是3,然后我们来分析一下过程:
*执行fn函数,引擎想要获取fn的结果,所以对这个函数发起了一次RHS查询
*将实参3赋值给形参a,也就是a = 3,这里的a进行了一次LHS查询
*然后b = a,这里对b进行了一次LHS查询,注意!!!然后问题出现了!!!
*引擎对b进行LHS查询的时候,在当前作用域没找到,所以它就会在上级作用域中去找
*但是在上级作用域中同样没找到,这时候它就会在顶级作用域(就是这里的最外围作用域)中去创建一个变量b
*然后对a进行RHS查询,在作用域中找到a的值之后,赋值给b
*最后打印b,对b发起一次RHS查询,因为之前已经创建过并且给b赋值了,所以可以查询到b的值
*最后对console发起RHS查询并顺利打印出3
*/
// 再来看看下边这个例子
function fn(a) {
console.log(a + b);
}
fn(3);
/**
*这时候大家肯定会知道这样会报b is not defined的错,但如果分析一下为什么呢?
*首先先来看看都发生了几次LHS以及RHS查询:
*执行fn函数,发生了一次RHS查询,会去当前作用域中查找函数fn,然后找到了
*接着进行了一次隐式操作,给形参a赋值实参3,也就是a = 3,那么这里就对a进行了一次LHS查询
*接着执行console.log(a + b)
*因为log方法想获取a + b的值,所以会对a和b分别进行一次RHS查询,对B进行查询的时候在作用域链中没找到,所以会抛出ReferenceError的错误.
*/
总结
- 作用域就相当于一个存储变量的仓库,作用域链就相当于仓库外边又套了一层仓库,如果在当前作用域(也就是当前仓库)没有找到变量的话,那么就会在上级作用于(外层仓库)去找,就这样一层层找,直到顶级作用域。
- LHS和RHS,可以这样理解:如果查找的目的是对变量进行赋值,那么就是LHS查询。如果是获取变量的值,那么就是RHS查询
a = 3
//这里就是LHS查询,因为是对a赋值了
console.log(a + b)
//这里就是RHS查询,因为log这个方法想要获取a+b的值.