前言
在看本文前默认你已经了解AO、GO是什么以及生成机制,如不懂可以先看我总结的JS中的预编译(AO、GO详解)
一、作用域、作用域链是什么?
首先,我们先明确一下作定义:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。这话可能有些官方,不要紧,我们抓住一个关键词‘函数’,也就是说作用域是针对于函数提出的。那么好,我们知道JS是一门面向对象的编程语言——函数也是一种对象类型,那么既然是对象,就要有它的一些默认属性:比如说.length / .name ,除此之外还有一些属性我们无法访问的,只能由JS引擎访问的到,[[scope]]就是其中的一个,它的作用就是存储作用域链的容器,其实也就是存储AO、GO的容器。要注意每执行完成一个函数AO会被销毁,再执行再生成再销毁。
二、如何判断一个函数的作用域
其实判断过程就是研究AO、GO之间的链式关系,只要弄清他们的链式关系,就可以正确的写出我们的作用域链,也就能够正确判断一段代码的执行过程了,(面试题从此so easy~)是不是很心动,那我们就一起来看一下判断步骤吧!
一个实例
function a()
{
function b()
{
var b=2
}
var a=1
b()
}
var c=3
a()
好了,发挥创建AO、GO对象的本领的时候到了:
GO初创建
{
c:undefined
a:function(){...}
}
GO执行
{
c:undefined ->3
a:function(){...}
}
前言里说过[[scope]]中保存作用域链,所以此时该作用域链的第0位即为GO对象
接下来创建a函数的AO
AO
{
a:undefined
b:undefined->function(){...}
}
创建好了,它肯定也是要存储到[[scope]]中的,那么重点来了它将存放于GO上方位于顶端,变成第0位(顶端),GO变为第1位(底端)。执行时自上而下。
所以:对于AO的scope这个属性来说,它有两项:第0项为自身AO,第1项为全局GO。
但,我们注意到,b(){}函数在a中也执行了,所以我们在b()这条语句执行前还要创建b()的AO对象:
AO
{
b:undefined->2
}
那么它的[[scope]]属性是只有一个它本身AO吗?还是加上全局GO?
答案都不是,其实对于b来说它的’GO’就是a的AO,因为b在定义时所处环境为a的函数环境之下,所以此时定义b时[[scope]]的第0项为a的完整作用域链,在b执行时它的作用域链第0项为它本身AO,第1项为a的完整作用域链。
总结:定义时即为上级环境的作用域链,执行时加上自己。
当b执行完毕,自身AO销毁:
而对于这个例子来说,b执行完,a也执行完,所以:
但要注意b的[[scope]]完全被销毁,。
总结:上级执行时,本级函数在被定义且生成了(当前环境下的GO+(AO))作用域链->本级执行生成AO
举例一:
function a()
{
function b()
{
function c()
{
}
c()
}
b()
}
a()
a定义时 a.[[scope]] 0位: GO a:function(){}
a执行时(b定义) 0位: AO b:function(){}
1位: GO a:function(){}
b.[[scope]] 0位: AO b:function(){}
1位: GO a:function(){}
b执行时(c定义) 0位: AO c:function(){}
1位: AO b:function(){}
2位: GO a:function(){}
c.[[scope]] 0位: AO c:function(){}
1位: AO b:function(){}
2位: GO a:function(){}
c执行时 0位: AO c:function(){}
1位: AO b:function(){}
2位: GO a:function(){}
执行完依次销毁,不再赘述。
总结
每个函数都有一个作用域链属性[[scope]]当函数被定义时,在里加入其当前环境,当函数被执行时再加入自己的AO,函数执行完,自己的AO将会被自动销毁。