什么是作用域
作用域(scope)是指程序源代码中定义变量的区域,简单来说,一段程序代码中所用到的变量并不总是有效的,而限定这个变量的可用性的代码范围就是这个变量的作用域。
在JavaScript中使用的作用域是词法作用域(静态作用域),特点就是变量的作用域在变量定义时确定。下文所说的作用域都指代词法作用域。
全局作用域
全局作用域是最外层的一个作用域。是根据ECMAScript实现所在的宿主环境而改变的,在浏览器中,全局作用域就是Winodw对象,node则是global对象。
在全局作用域的变量可以在所有作用域中被访问,假如将全局作用域比作中国,中国人这个属性就拥有了全局作用域,无论你是湖北人还是武汉人都可以说自己是中国人,
在JAVAScript中
1.window(global)
global.country ='Chinese'
function province (){
var province = '湖北'
console.log('province')
}
province() //Chinese
2最外层的变量或者函数
var country = '中国'
function province(){
var province = '湖北'
return province
}
function city(){
var city ='武汉'
console.log(contry,province(),city)
}
city() //中国 湖北 武汉
3.未定义直接赋值的变量
function province(){
var province = '湖北'
country = '中国'
return province
}
function city(){
var city ='武汉'
console.log(contry,province(),city)
}
city() //中国 湖北 武汉
局部作用域
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的是函数内部。
函数作用域
定义在函数中的变量就处于函数作用域中。不同函数作用域中,变量不能相互访问。还是按照上面的例子举例,
如果你是浙江人,你可以说你是中国人,但是你不能说你是福建人。当然如果你是武汉人,你可以说自己是湖北人,因为湖北的函数作用域包含了武汉的函数作用域。
简单来说就是里面的可以访问外面的作用域,但是不能访问里层的和同级的作用域
接下来在学习作用域链前 我们需要先学习预编译
预编译
执行期上下文 :一个函数在运行之前,创建一块内存空间,这个空间中会存放所有该函数执行所需要的用到的数据,为该函数提供支持;this,AO,arguments[0]
// 变量声明提升 //函数声明整体提升
函数预编译:
从函数执行的前一刻开始:
1.创建一个函数的AO对象(Activation Object),执行期上下文对象
2.函数的形参,成为AO对象的属性,值为实参的值,若未传值,值为undefined
3.将var关键字声明的变量,成为AO对象的属性,值为undefined,遇到重名,不做任何变化
4.将function声明的函数(函数声明)成为AO对象的属性,值为函数体,重名直接覆盖
全局预编译:
1.创建一个函数的GO对象(Global Object),执行期上下文对象
3.将var关键字声明的变量,成为AO对象的属性,值为undefined,遇到重名,不做任何变化
4.将function声明的函数(函数声明)成为AO对象的属性,值为函数体,重名直接覆盖
作用域链
消化吸收了之前的预编译之后,我们就可以去了解作用域链了,作用域链最泛用的场景就是函数,所以下面例子都是以函数为主体,话不多说,先看例子。
var z = 1
function tim(){
function cope(){
var x= 2;
y=3
}
var y = 4
cope()
console.log(y) // 3
}
tim()
运行代码之后我们就可以发现输出为3,那么作为cope作用域中的值3是怎么在tim作用域中输出的呢?
答案就是作用域链。
每个JavaScript对象都有属性,有些可以被我们访问,有些却不行,这些属性仅供js引擎存取,而我们接下来要讲的[[scope]]就是这样的一个属性。
当函数被定义时它就被绑定了[[scope]]属性,而[[scope]]中存储的就是执行上下文的集合(GO | AO),从作用域链的顶端依次向下查找其呈链式链接,我们称之为作用域链。让我们再回头来看之前的例子
var z = 1
//1.tim 定义 tim.[[scope]] = GO:{z:1,tim:[Function: tim]}
function tim(){
//3.cope定义 cope.[[scope]] = tim.[[scope]] = tim(AO):{cope:[Function: cope],y:undefined}=>GO
function cope(){
var x= 2;
// 6.cope(AO)上没有y属性,就会沿着作用域链往上找,一直没有就会挂载在GO上
y=3
}
//4.tim(AO):{cope:[Function: cope],y:4}
var y = 4
//5.cope.[[scope]] = cope(AO):{x:undefined}=>tim(AO)=>GO
cope()
//7.tim(AO):{cope:[Function: cope],y:4}
console.log(y) // 3
}
//2.tim 执行 tim.[[scope]] = tim(AO):{cope:[Function: cope],y:undefined}=>GO:{z:1,tim:[Function: tim]}
tim()
按照代码运行的顺序大致就是如此,不熟悉的可能需要三五分钟仔细消化一下这个流程。
简单总结一下
1.作用域链存储的就是执行上下文的集合
2.当前作用域中没有使用未在在作用域定义的变量时,会沿着顶端依次向下查。