作用域
作用域是指程序的源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript采用词法作用域(lexical scoping),也就是静态作用域
注:好像没听过语法作用域
作用域有静态作用域(词法作用域)和动态作用域之分。它们之间的区别是:静态作用域是在定义的时候确定的,动态作用域是在运行时确定的。前者关心函数在何处声明,后者关心函数在何处调用。
具体的情况也就是:遇到既不是形参也不是函数内部定义的变量的时候,静态作用域去函数定义时的环境中查询,动态作用域去函数调用时的环境中查询。
举个例子:
1 | var value = 1; |
简单分析一下,若是静态作用域,函数foo执行的时候,函数内部查找变量value的值,不是形参,内部也没有,这个时候就从foo函数定义的区域开始找,所以输出1;若是动态作用域,则从函数调用的区域开始找,也就是bar函数内部,所以输出2。
JavaScript采用的是静态作用域,所以这段代码输出1。
JS的作用域链
在JS进阶系列-第四篇-执行上下文 和 JS进阶系列-第五篇-变量对象 中我们知道,在查找变量时,会从当前上下文的变量对象中查找,如果找不到,就会从父级(词法层面的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行环境的变量对象构成的链表就是作用域链。
注意:这里的父级执行上下文是依据词法作用域的规则,不是执行阶段的执行栈中的下一级。
举个例子:
1 | var scope = "global scope"; |
执行栈的状态变化
checkscope执行上下文的作用域链
- 在全局执行上下文中,checkscope函数被创建(注意此时还未进入到checkscope的执行上下文中),全局上下文的变量对象被保存到checkscope的[[scope]]中。
1
2
3checkscope.[[scope]] = [
globalContext.VO
]
这一步就是关键,父级(词法上的,这里是全局)执行上下文创建的时候,里面的函数checkscope就会保存当前上下文的变量对象(全局变量对象)。即使父级的执行上下文从执行栈中抛出,函数checkscope的上下文依然可以获取到父级的变量对象,这也就是闭包,当然这个例子没有形成闭包。不过从这一步看,正是因为词法作用域的规则,才导致了有闭包的概念,闭包可以说是为了实现词法作用域的一种设计。
2.checkscope函数执行,创建checkscope执行上下文并进入执行栈。checkscope上下文有创建和执行阶段,在创建阶段会创建变量对象、生成作用域链、确定this的指向。作用域链的初始状态就是父级环境的生成时产生checkscope.[[scope]]
1 | checkscopeContext = { |
3.checkscope变量对象生成,此时checkscope上下文处于执行栈顶部,变成活动对象
1 | checkscopeContext = { |
4.活动对象压入作用域链的顶端
1 | checkscopeContext = { |
5.checkscope上下文执行阶段,赋值语句、函数调用以及其他语句
1 | checkscopeContext = { |
闭包
闭包是指那些能够访问自由变量的函数。自由变量指的是在函数中使用,但既不是函数的参数,也不是函数的局部变量的变量。
也就是说,如果一个函数使用了自由变量,就构成了一个闭包。
也就是说:
1 | var a = 1; |
foo函数访问了变量a,但a既不是foo的参数,也不是foo内部定义的变量,所以这不是构成了一个闭包吗?
没错,这确实构成了闭包。所以在《JavaScript权威指南》中就讲到:从技术角度讲,所有的JavaScript函数都是闭包。
从理论上讲,所有的函数在创建的时候就已经将上层环境的数据(变量对象)保存起来了([[scope]]属性),这不就是为了实现词法作用域的规则嘛。
这么一看闭包好像没什么特殊的。但闭包却有一个常见的应用场景,这个才是我们口中经常讨论的实践层面的闭包。
** 函数的上层执行上下文已经销毁,已经从执行栈中抛出去了,但是函数依然可以访问上层环境的数据(变量对象)**
举个例子:
1 | var scope = "global scope"; |
执行栈的状态:
可以看到,在foo
方法执行访问变量scope
的时候,执行栈中没有checkscope
的执行上下文,但是依然可以访问checkscope
中的变量。因为checkscope
上下文曾经进入过执行栈,那个时候f
函数被创建,同时f函数就已经保留了checkscope
执行上下文的数据(变量对象)。f
函数的作用域链
1 | fContext = { |
参考链接
JavaScript深入之词法作用域和动态作用域
JavaScript深入之作用域链
JavaScript深入之闭包
四、作用域链与闭包