JavaScript:浅谈闭包及其回收原则

前言

在JavaScript这门语言中,有一个非常重要但又难以掌握,一个近乎神话的概念,闭包。如果你对词法作用域有一定的理解,那么闭包的概念几乎是不言自明了。
回忆我写了这么久的JavaScript代码却完全不理解闭包是什么,直到最近读了《浏览器原理》和《你不知道的js》才对闭包有一个比较清晰的认识。看完我的这篇文章,如果你能掌握闭包,必将功力大增。

理解闭包

定义:在JavaScript中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数
已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们把这些变量的集合成为闭包。比如外部函数是foo,那么这些变量的集合就称为foo函数的闭包。
(不理解定义没关系,先往后看)
我们先看一段代码:

 function foo() {
            var a = 2

            function bar() {
                console.log(a) // 2
            }
            bar()
        }
        foo() // 2

这是个简单的函数嵌套的例子,基于词法作用域的查找规则,函数bar()可以访问外部作用域中的变量a。
从技术上来讲,这也许是一个闭包,但如果根据定义来说,它并不是闭包(因为bar函数是在foo()函数内部执行的)。最准确的解释应该是bar()对a的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分。
从纯学术的角度来讲,在上面的代码中,函数bar()具有一个涵盖foo()作用域的闭包。
但是通过这种方式定义定义的闭包并不能直接进行观察,也无法知道这个闭包是如何工作的。
再看下面这段代码:

function foo() {
            var a = 2

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

在这段代码中,我们将bar()函数本身当作返回值,在foo()执行后,其返回值赋值给变量baz(),实际上只通过不同的标识符引用调用了内部的函数bar()。
bar显然能够正常被执行,但是它是在自己定义的词法作用域以外的地方执行的,在foo()执行以后,通常期待foo()的整个内部作用域都被销毁(Js引擎的垃圾回收机制)
但是因为闭包的存在,事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是bar()本身在使用。现在再回头看闭包的定义,是不是就很明确了。
我们也可以通过“开发者工具”来看看闭包的情况,复制上段代码->在编辑器里面运行->F12->source,给console.log(a)打断点->刷新会看到:


右侧便是内存中闭包的情况。

闭包的回收

现在我们知道闭包是什么了。那么如果反复的产生闭包,就有可能会造成内存泄漏,那么了解闭包是如何被回收非常重要。
1.如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但是如果闭包以后不再使用了,就会造成内存泄漏。
2.如果引用闭包的函数是一个局部变量,等函数销毁以后,在下次JavaScript引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么JavaScript引擎的垃圾回收器就会回收这块内存。
所以,在使用闭包的时候,你要尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在,但如果使用频率不高,而且占用内存又比较大的话,那就尽量让他成为一个局部变量。
关于JavaScript垃圾回收机制,后面我会专门出一篇博客来详细的讲解的。

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/XF-eng/p/14202047.html