作用域
作用域是可访问对象的集合,确定当前执行代码对变量的访问权限。
作用域可分为静态作用域和动态作用域,JavaScript采用静态作用域,也叫词法作用域。
静态作用域
函数的作用域在函数定义的时候就决定了,与函数如何被调用,在何处被调用无关。
var a = 1; function foo() { console.log(a); // 1 } function bar () { var a = 2; foo() } bar()
以上代码,foo函数为打印出1。虽然foo是在bar函数中执行,但它的作用域是在它定义时就确定了。首先查找foo内部有没有a,没有就从外层找a,于是最终打印出了1。
[[scope]]
在将作用域链之前,先讲讲[[scope]]。用console.dir随便打印一个函数
function bar () { var a = 1; function foo () { console.log(a) } console.dir(foo) } console.dir(bar) bar()

可以看到有一个[[scope]]属性。从这个例子可以看出:
- [[scope]]是所有父变量对象的层级链
[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。
Q:有一点需要注意下,以上例子中如果foo函数中不使用bar中的变量,也就是如果不加
console.log(a)
,则foo的[[scope]]中不会有bar这一级。
A:这一点也很好理解,js在编译时就发现foo不会引用到bar中的变量,所以不将bar的作用域加入foo的父级层级中,因为没必要;但foo的可访问层级还是包括bar的,只要有使用到bar中的变量,bar就会加入foo的[[scope]]中。这一点从某种程度上说,也正好进一步说明了[[scope]]是在函数定义时(js编译时)就确定好了。
结合上一节所说的:js采用静态作用域,函数的作用域在定义时就确定了;[[scope]]也是在函数定义时就确定了,是函数的一个属性而不是上下文,与如何调用无关。
作用域链
定义
作用域链在进入上下文时被创建,定义为:当前上下文+[[scope]],即
Scope = AO|VO + [[Scope]]
也就是,将当前执行上下文的变量对象置于作用域数组前端;当查找变量时,首先查找当前AO|VO中是否存在,然后沿着函数定义时的层级([[scope]])查找,直到最后的Global。
实践
文章深入js——变量对象中提到了利用控制台的scope查看变量对象,这下我们就可以真正的好好看看scope了。
function bar () { var a = 1; function foo () { debugger var b = 2; console.log(a) } foo() } bar()

可以看到,scope的最上层是Local,也就是当前执行上下文的变量对象,下面就是函数的[[scope]]属性里保存的父级层级链。点击Call Stack中的函数,还可以切换当前执行上下文,观察下面scope的变化。
整体流程
讲了变量对象和作用域,最后我们整体梳理下函数执行时发生了什么,整体流程是怎样的。
var a = 1; function bar () { var a = 2; console.log(a) } bar()
- bar函数被创建,同时将父级作用域保存到其属性[[scope]]中
bar.[[scope]] = [ globalContext.VO ]
- 进入bar函数,创建bar执行上下文,bar执行上下文被push到执行上下文的栈顶
Stack = [ barContext, globalContext ]
- 赋值bar函数的[[scope]]属性并创建作用域链
barContext = { Scope: bar.[[scope]], }
- 用 arguments 初始化活动对象,并加入形参、函数声明、变量声明
barContext = { AO: { arguments: { length: 0 }, a: undefined }, Scope: bar.[[scope]], }
- 将活动对象AO压如作用域链顶端
barContext = { AO: { arguments: { length: 0 }, a: undefined }, Scope: bar.AO.concat(bar.[[scope]]), }
- 执行bar函数
来源:https://www.cnblogs.com/youhong/p/12212755.html