至简·作用域及作用域链

说起作用域,先说说作用域的类型吧,对于不同的语言,它们的作用域类型也许不同,主要分为词法作用域(静态作用域)和动态作用域。

  • 词法作用域是在函数定义时就已确定。也就是在我们书写代码时,就已经确定了,然后在我们执行函数时,再遇到既不是形参也不是函数内部定义的局部变量的变量时,就会到我们定义时的环境中寻找所需的变量。
  • 动态作用域是在函数调用时才确定的。也就是说再遇到既不是形参也不是函数内部定义的局部变量的变量时,函数在哪里调用,就在那里寻找所需的变量。它与变量的值,与它定义在哪里无关,与它在哪里调用有关。

说实话动态作用域,我是基本没用过,也许用过也忘了。以一张图为解释。
在这里插入图片描述
好了,简单的理解了什么是词法作用域以及动态作用域,我们就开始针对javascript进行分析。先说说我们开篇提到的问题,什么是作用域?
之前对于这个知识点,的确属于雾里看花的那种感觉,,在网上基本都是一个理解方式,就是function内部就是作用域。(O_o) ?? 你当我胖虎好糊弄是不是。不过说实话,这话,也没错。但是肯定没这么简单啊。通过我胖虎不断的翻阅,个人呢也产生了一些小小的理解。一下就是我对作用域的理解。
个人理解:作用域其实就是声明变量时,所在的区域(包括变量声明和函数声明)。这片区域保存了当前环境下的执行期上下文。
我们在声明变量,或者声明一个函数时,其实就存在于一个环境中。如果是在一个函数内部,那么也就是这个函数就是当前所处的作用域,如果是在全局中声明变量,那么所处的就是全局作用域。也许你已经猜到,那么就是有变量作用域和函数作用域两种喽,对不起,我们本次仅讨论ES6之前的版本,咱先不提let,好吧!对于ES5之前,只有函数作用域。也就是说每当声明一个函数,也就创建了一块作用域。而这块作用域保存着的也正是当前环境中的执行期上下文。这一点也不难理解,处于函数内部,而函数执行时,就会生成执行期上下文,而作用域正是保存的这个环境,那也就是这片执行期上下文。
那么作用域是在哪里保存的呢,在函数有一个内部属性,即[[scope]]属性,在函数创建之时,就会保存所有的外部变量到其中,这是因为js是词法作用域的缘故。所以如果存在多层函数嵌套,就会产生一个保存外部对象的层级链。当函数运行,查找变量时,就会从当前保存的执行期上下文中查找,如果没有,就会继续查找所当前保存的执行期上下文所处的作用域,也就是向词法层面上的父级作用域查找,一直向外找,向外找,直到找到全局的执行期上下文中。像这样的有多级上下文变量对象构成的这种链式结构,也就是作用域链的概念。

说的再多,不如举个栗子:

function foo(){
	function bar(){}
}

我们来观察一下这两个函数,分别所处的作用域是什么:

foo.[[scope]] = {
	gloabelContext.VO//全局作用域的执行期上下文
},
bar.[[scope]]={
	fooContext.AO,//外部的foo函数
	gloabelContext.VO //更外层的全局执行期上下文
}

以上这种的函数作用域是函数还未执行时的作用域,函数被创建时会将其所处的作用域保存到自身的[[scope]]属性中。当函数激活时,进入函数的执行期上下文在函数执行的前一刻,函数被激活。要发生几件很重要的事,函数会构建完整的作用域链,也就是将自身也要加入其中。步骤如下:
创建活动对象,然后将活动对象添加到自身作用域链的最顶端,最终foo所保存的作用域链就是形如 Scope:[AO,[[scope]]],这样的一个作用域链,最前面的,也就是作用域链的最顶端的,是当前函数的执行期上下文内的活动对象AO,并且还携带着上一层的作用域,像一条链子。

我们来具体分析分析,这条链是怎么生成的。

  1. 函数foo被创建,保存所处的作用域链,到其内部属性[[scope]]中
foo.[[scope]] = [
	globalContext.VO
]
//作用域链所处的环境是一个栈结构,为了方便于理解,暂且以数组的形式呈现
  1. 执行foo函数的前一刻,产生对应的执行期上下文,然后将所产生的执行期上下文压入ESC执行栈中
ESC=[
	checkscopeContext,
	gloabelContext
]

3.foo函数执行前,不立即执行,开始准备工作。

  • 第一步:复制自身的[[scope]]属性,准备创建作用域链
fooContext{
	scope:foo.[[scope]],
}
  • 第二步:函数被激活,创建foo的活动对象AO,即各种形参、函数、变量的声明,并被arguments对象初始化。
fooContext{
	AO:{
		arguments:{
			length:0
		},
		......
		bar:function bar{}
	}
	scope:foo.[[scope]]
}
  • 第三步:将活动对象AO压入作用域链链的最顶端。
fooContext{
	AO:{
		arguments:{
			length:0
		},
		......
		bar:function bar{}
	},
	scope:[AO,[[scope]]]  
	//这里保存的就是自身的AO,以及foo所处的作用域(全局作用域),生成完整的作用域链
	//写成上面那样可能不太好理解,这样写 scope:[ AO , foo.[[scope]] ] ,这样就很明显了
}

至此准备工作结束,开始函数的执行,即操作AO中的属性值,要查找变量就会先查找自身的AO中是否有,如果没有就会沿着保存的作用域链向上查找,直到全局作用域中。最终代码执行结束,执行期foo的上下文从ESC中弹出,被回收。

ESC=[
	globalContext
]

在这个环节中我没有分析bar函数的执行过程,因为bar函数执行的过程以及作用域链的产生原理与foo的是一样的,
可以直接给出一个结果:

barContext:{
	AO:{
		length:0
	},
	scope:[ AO,bar.[[scope]],foo.[[scope]] ]  
	//bar自身AO,bar所处作用域也就是 scope:[ AO , bar.[[scope]] ] (foo的),foo.[[scope]](全局的)
	//可以看到bar的作用域链中保存着自身的AO,以及foo的AO和foo保存的全局上下文,所以就可以沿着整个作用域链向上查找所需的变量。
}

再贴上一幅图,以便于理解
在这里插入图片描述

   最后再举一个例子,这个作用域链,其实就类似于一个家族的财富链,爷爷辈的人自己打拼下的财富就类似于全局作用域,父辈,自出生的那一刻就身处于爷爷辈打拼下的财富中,其次父辈又会产生自身的财富,父辈有需要,先找自己的,自己没有了,可以向父辈要。然后再到子辈,子辈自出生的那一刻,即直接身处于父辈打拼下的财富中,其次也能获取,爷爷辈的财富,最终,子辈也将创造自己的财富,像这样构成的这种财富的层级链,与作用域链的方式基本一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值