每日读《你不知道的JavaScript(上)》| 闭包

145 阅读4分钟

自我审视

其实我前面写过一些关于闭包的文章,但总感觉每次写都会有一些新的理解。

就像我读完这本书关于闭包的解读之前,我认为闭包单纯只是一种保持对外部作用域引用的机制。

然而也不会发觉,彻底理解闭包其实需要非常扎实的作用域理论基础。

我之前不太关注这些。

image.png

那么下面就对闭包来做一个“重生式”了解吧。

闭包是什么

原文中的描述是:

当函数可以记住并访问所在的词法作用域时,就产生了闭包。

所以闭包其实不一定保持的是对外部作用域的引用,只要能够记住并访问函数定义时所在的词法作用域,就会产生闭包。

来个 Case:

function foo() {
    var a = 2;
    
    function fn1() {
        console.log(a);
    }
    
    return fn1;
}

var fn = foo();
fn(); // 2 ---这就是闭包的效果

如果想要理解闭包,我觉得关键是要理解这里面的引用

我们用逆向思维来理解,把上面这段代码先改成如下:

function foo() {
    var a = 2;
    
    function fn1() {
        console.log(a);
    }
    
    fn1();
}

foo(); // 2

这段代码很好解释吧,调用了 foo 函数。

然后执行 foo 函数体。

foo 函数体中定义了 a 和 fn1,且调用了 fn1。

最后在函数体内执行 fn1。

根据变量查找规则,内层作用域能够访问外层作用域中的变量,逐级往上查找。

fn1 内没找到 a,在 foo 中找到了,便打印出 a 的值 2。


好。

Question:

那我想通过不在 foo 内部调用 fn1 的方式,如何能够在全局作用域中正确打印出 a 呢?

在上面的 Case 中,因为 foo 执行完之后里面的变量会被垃圾回收掉,所以外部访问不到 a 了。

那既然我们想在外面正确打印这个 a,那么我们的思路就应该转向:

如何做才能让 foo 还保持对 a 的引用呢?

怎么才能不让垃圾回收机制销毁这个变量呢?

如最开始那段代码,所要做的核心就是,在定义 fn1 的作用域之外,使用它。

作为 foo 的返回值,然后在全局下赋值给另一个变量 fn,再执行 fn 就相当于在全局下执行内部的 fn1。

而在这其中,fn1 仍然持有对 foo 所创建的作用域的引用。

这个引用就叫闭包。

这个函数在定义时的词法作用域以外的地方被调用。

记住这个规则也许有助于你继续深入理解闭包。往下看。

理解闭包,成为闭包!

这个是书中的例子,已经很好很简洁了,就拿这个说事儿!

function foo() { 
    var a = 2; 
    function baz() { 
        console.log( a ); // 2 
    } 
    bar( baz );
} 
function bar(fn) { 
    fn();
}

(搓手手)这里面有个闭包哦,找找看。

image.png

闭包产生在 bar 里面调用 baz 的那一刻。(fn调用那里)

为什么?

因为 baz 能够记住声明它的位置并访问它本身所在的作用域。

无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

再来看一个经典的 Case。

function wait(message) { 
    setTimeout( function timer() { 
        console.log( message ); 
    }, 1000 ); 
}

wait( "Hello, closure!" );

这里也藏着个闭包。

我看第一遍没看出来有啥异样😂。

然后才发现,setTimeout 是一个异步函数对吧,那其实 setTimeout 的回调函数 timer 是在 wait 函数执行完毕之后才执行的

但是 wait 却能在 1s 之后打印出 Hello, closure!

这说明了 timer 保持着对 message 的引用,没有在 wait 执行完之后被销毁哦。

这里便形成了一个闭包。

现在大致应该能够识别哪些代码里藏匿着闭包了。

小结

这一章花了两天写,咳咳其实是昨天出去吃东西了hhh,偷了一天懒。

不过闭包确实比较重要呀,之前面试笔试都总是考。

这一章主要介绍了闭包的概念、深入理解闭包和闭包的应用场景。

确实,闭包不是一个罕见的东西。

项目中经常写着写着就有了。只不过我们没发现它。

哈哈,明天继续干巴爹~