上一篇文章说到作用域链,其实作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。那到底是怎样运作的呢?
[[scopes]]分析
那么自己就在思考,上面说的都是理论知识,内部访问外部,在代码上是不是有上面地方存储了,好奇的我打印了一个匿名函数如下:

可以看到,匿名函数里面有个[[Scopes]],这就这篇文章的主角了,[[Scopes]]里现在有个全局对象。
scopes里面会存在几种类型:Global,Closure,Script,Block。Closure后面那个就是对应的闭包名称,没有就是匿名闭包。
[[scopes]]到闭包
我们经常说闭包,也知道闭包就是一个函数,最早开始自己一直对这个解释不太理解——闭包是指有权访问另一个函数作用域中的变量的函数。那么我们从[[scopes]]来解释一下。构造一个所谓的闭包:
function a () {
var a = 1;
function b () {
console.log(a);
}
b();
console.dir(b);
}
a();打印如下:

Closure中文就是闭包,可以看到b环境下有a的闭包变量对象,在访问得时候从[[scopes]]第一个对象开始访问,这就是闭包,有权访问另一个函数(a)作用域中的变量的函数(b)。
[[scopes]]到经典面试题
前端经典面试题——for循环,在红皮书上面也有这个例子。
function createFunc() {
var result = [];
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}这个函数我们的本意是想得到一个数组,数组里每一项都是一个函数,函数返回值是对应的序号,想象是这样的,但是实际情况我们都知道不是。根据上面的理论知识,我们截图看看scopes里面是什么:

可以看到,[[scopes]]里面两个对象,一个是全局对象,一个是Closure对象,先查找Closure,会发现i值是函数里面i循环最后的那个值,这也符合红皮书里面说的:
闭包只能取得包含函数中任何变量的最后一个值。闭包保存的是这个变量对象,而不是特殊的变量
这Closure保存的值,也就能说明为什么我们打印出来都是同样的10,那么想要得到我们理想的效果,我们应该怎么做?
既然现在的i读取的是createFunc的作用域,那么i肯定是固定的,那么可不可以构造一个i值一直根据循环变化的作用域,我们去读取这个函数的作用域呢?
答案可以的, 就有了经典的构造另一个匿名闭包的办法,这里就不贴代码了,网上很多。
然后,es6的出世,也很好的解决了上面的问题,我们知道let和const是有块级作用域的,那么用let来解决有从原理上有上面不同吗?将上面的var换成let,我们来打印下:

可以看到,不同的是这里的作用域是Block作用域,而不是Closure作用域了,Block里面保存的值就符合和我们的期待了。
[[scopes]]也就从源头解释了,我们之前的经典面试题,为什么可以和为什么不可以,其实这仅仅是九牛一毛,我们遇到的所有读值问题都能用此解释。
延伸——抛砖引玉
比如这里再抛砖引玉一下,大家可以去发现身边更多的例子:
es6我们都知道,let不存在变量提升,如果在声明之前调用,那么肯定会报错如下:

但是,为什么呢?
同样的,我们从[[scopes]]找原因,可以发现里面确实没有a这个值,但是如果用var,就可以找到,这也就解释了为什么报错了。
好啦,喜欢的朋友可以点点小红心,有问题我们可以沟通交流。