[[scopes]]结合实际分析

上一篇文章说到作用域链,其实作用域链的本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。那到底是怎样运作的呢?

[[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,就可以找到,这也就解释了为什么报错了。

好啦,喜欢的朋友可以点点小红心,有问题我们可以沟通交流。


版权声明:本文为viewyu12345原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。