深入js——作用域链

作用域

作用域是可访问对象的集合,确定当前执行代码对变量的访问权限。
作用域可分为静态作用域和动态作用域,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函数
posted @ 2020-01-19 10:48  小丸子的城堡  阅读(275)  评论(0编辑  收藏  举报