大厂面试题系列(二)-作用域进阶
前言
本文主要会对系列(一)所讲解的作用域进行更深一层的讲解,包括作用域的查找规则,执行上下文和作用域链三个方面的内容。
一.作用域的查找规则
在上文中,我们提到作用域的相关概念:作用域是一套设计良好的规则来储存变量,并且之后可以方便的找到这些变量。那么作用域是如何查找和访问变量的呢?在本节中会给出答案。
1.演员表和对话
在学习作用域的过程中,我们可以将整个过程模拟成几个人物之间的对话。那么首先我们先来认识一下这场对话中的的‘演员们’,先来一段代码:
例1.1
var a = 2
先介绍一下演员表:
-
引擎:从头到尾负责整个JavaScript 程序的编译及执行过程
-
编译器:引擎的好朋友之一。负责语法部分分析及代码生成等脏活累活
-
作用域:引擎的另一位好朋友,负责收集并维护所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,以确定当前执行代码对这些标识符的访问权限。
例1.1是一个简单的声明赋值语句,那么当这句代码执行的时候,演员们是如何工作的呢?
在执行变量的赋值操作时,会执行两个工作:
-
首先编译器会在当前作用域中声明一个变量(如果之前没有声明过)
-
在运行时,引擎会在作用域中查找该变量,如果能找到就会对它进行赋值
也就是说在例1.1当中,首先var a 声明一个变量a,然后引擎在作用域中查找到变量a,进行a = 2 的赋值操作。
2.LHS 和 RHS
在例1.1当中,引擎会为变量a进行LHS查询,另外一个查找类型叫做RHS。
顾名思义换句话说:当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时,进行RHS查询。
-
LHS:试图找到变量的容器本身,从而对其进行修改(赋值)
-
RHS:简单的查找某个变量的值,不用对其进行操作。
那么这么讲,你是否明白了什么时候用LHS,什么时候用RHS呢?
3.引擎和作用域的对话
上面讲到了引擎会为变量进行LHS或RHS,在这个过程中,作用域发挥一个怎么样的作用呢?先来一段代码:
例1.2
function foo(a) {
console.log(a); // 2
}
foo(2)
让我们来看一段引擎和作用域之间的对话:
引擎:我说作用域,我需要为foo 进行RHS 引用。你见过它吗?
作用域:别说,我还真见过,编译器那小子刚刚声明了它。它是一个函数,给你。
引擎:哥们太够意思了!好吧,我来执行一下 foo。
引擎:作用域,还有个事儿。我需要为a进行LHS 引用,这个你见过吗?
作用域:这个也见过,编译器最近把它声明为foo的一个形式参数了,拿去吧。
引擎:大恩不言谢,你总是这么棒。现在我要把2赋值给a。
引擎:哥们,不好意思又来打扰你。我要为console 进行RHS 引用,你见过它吗?
作用域:咱俩谁跟谁啊,再说我就是干这个的。这个我也有,console是个内置对象。给你。
引擎:么么哒。我得看看这里面是不是有log(..)。太好了,找到了,是一个函数。
引擎:哥们,能帮我再找一下对a的RHS引用吗?虽然我记得它,但想再确认一次。
作用域:放心吧,这个变量没有变动过,拿走,不谢。
引擎:真棒。我来把a的值,也就是2,传递进log(..)。
就是上面这段经典的对话,生动的描绘出了引擎和作用域是如何配合工作的看到这里相信你已经对作用域的查找规则有个清晰的认识了。
所以在这里留下一道题给大家练练手:
例1.3
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2)
请问在例1.3中有几处LHS查询,几处RHS查询呢?欢迎大家在评论区留下答案。
二.作用域链
1.作用域嵌套
还是这张熟悉的图,在图中我们可以看到最内层的bar函数作用域,中间有foo的函数作用域,最外层存在全局作用域,那么这样一个嵌套的过程,就被称为作用域嵌套。
所以什么叫做作用域链呢:当引擎从当前的作用域开始查找变量,如果找不到,就会向上一级继续查找,直到找到变量或者已经到达全局作用域仍然找不到就会停止,那么在这个查找过程中,会产生一个成链式连接的集合:储存着执行期上下文对象的集合,这种结构就可以称为作用域链。那么什么是执行上下文呢?我们接着往下看
三.执行上下文
那么什么又是执行上下文呢?
在作用域中,储存着运行期的上下文:当函数执行时会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文会被销毁
先来一段代码:
例3.1
function a () {
function b() {
var b = 234
console.log(a)
}
var a = 123
b()
}
var glob = 100
a();
那么在上面这段代码执行时会产生什么呢?我们先看一张图:
-
首先是函数a声明的时候会产生一个 [[scope]],其中就包含着一个一个的执行上下文,这些集合就被称为scope chain(作用域链),因为函数a定义在全局,所以一定会有一个 Global Object(全局执行上下文),在全局上下文中,包含一些内置的键值对,包括this,指向window,以及我们在全局定义的变量glob,值为100.
-
当执行到a() 时,执行函数a,会产生a的执行上下文:
-
当a执行的时候(橙色线条),GO(全局执行上下文)在作用域链中会被挤到1的位置上,函数a自己的执行上下文(AO)会挤到最顶端,也就是0的位置,这也就是为什么查找变量是由内而外,从作用域链的最顶端开始查找。
-
最后在函数a当中执行函数b的时候,也是相同的规则:
- 红色的线条代表的就是函数b被执行的时候,0的位置存放着b的执行上下文,1的位置放着a函数a的执行上下文,2的位置上放着全局的执行上下文。
如果有文章中写的不够透彻或者有不明白的地方欢迎在评论区留言~ 本期就讲到这里了,下期见~