JS必学--闭包

60 阅读5分钟

一.什么是闭包

MDN中对于闭包的描述:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。懂了吧?不懂就对了,官方就是喜欢搞些极其正确标准的解释。大家都知道,在函数作用域外部是无法访问内部的对象或者变量的,但是闭包就可以,他就很牛,那是如何实现的呢?带着这个疑问我们来理解什么是闭包。我们看看下面这个例子:


        function Fun(){
            const num = 666
            function fn(){
                console.log(num);
            }
            return fn
        }
        const res = Fun()
        res()
        //------result:666

是不是可以很清晰的看到在函数Fun的外部访问了变量num?没错,这就是闭包的特性之一:可以在函数外部通过函数访问函数内部的变量。看到这有的同学就不服气了,js明明规定了在函数执行完之后它内部的词法环境(创建的所有变量)应该被销毁了啊,这里为什么还可以访问到num呢? 没错,js是这样规定的,但是我们仔细看会发下其实最后一行代码执行的不就是Fun()里面的fn函数嘛,也就是说函数fn仍然被res变量所引用,而fn执行所依赖的数据就是num,那你说咱都还没用完为啥要给我回收了呢?

二.闭包的特性

1 变量私有化

其实我们上面已经证明过了变量私有化的特性了(顺便提一句,私有变量就是那些函数可以调用但是外部无法直接获取的变量。),这里就不再赘述了。

2 持久化存储(用不好会造成内存泄漏的严重后果)

这个特性其实在收买了也提到过了,这里再给大家详细的说一下。认真看过第一节的小伙伴应该都知道js的垃圾回收机制无法回收函数Fun()里的变量了,这就达到了持久化存储的目的,那内存泄露一说呢?大家看下面这段代码:

       function Fun(){
            let num = 666
            return function(){
                return num
            }
        }
        let res1 = Fun()
        let res2 = Fun()
        console.log(res1());//666
        console.log(res2());//666

浏览器在读取到函数时,并非直接将函数体中代码执行,而是会将函数以字符串的形式存储在浏览器的内存中,这个过程浏览器会分配一块堆内存用于函数的存储,并记录函数存储的位置.也就是说res1和res2保存的位置在堆中是两块独立的内存,这样一来就有两块内存被长期占用而不被释放了,那十块,二十块....这里就不用我多说了吧,大家应该都懂了。

三.闭包的使用场景

1 防抖

光会理论不行啊,要在实际中使用才是真正理解了闭包。大家平时都写过防抖吧,我们来看一下不使用闭包的防抖函数:

        function deb(){
            if(timer) clearTimeout(timer)
            timer = setTimeout(()=>{
                fn()
            },1000)
        }
        function fn(){
            //xxx
        }

大家可以看到我们的timer是在全局作用域下声明的,在实际开发中有很多地方都会使用防抖,也就是说每个防抖使用的地方都要重新创建一个timer,是不是造成了全局变量的污染?而我们的闭包是不是有个变量私有化的特性?这不就ok了吗?那我们来看看使用闭包的防抖:

        function debounce(){
            let timer = null
            return function(){
                if(timer) clearTimeout(timer)
                timer = setTimeout(()=>{
                   fn()
                },1000)
            }
        }
        function fn(){
            //xx
        }
        const deb = debounce()

这样写timer就成为了防抖的私有变量,那要用防抖直接调用封装好的防抖函数不就好了(说出现内存泄漏的小伙伴别抬杠哈qaq)。

2 模块化

模块化直接上例子,下面这个例子很经典同是也非常易懂

        function Fun(){
            let num = 0
            function changeNum(val){
                num += val
            }
            return {
                increment(){
                    changeNum(1)
                },
                decrement(){
                    changeNum(-1)
                },
                result(){
                    return num
                }
            }
        }
        const res1 = Fun()
        const res2 = Fun()

        res1.increment()
        res2.decrement()

        console.log(res1.result());// 1
        console.log(res2.result());// -1

我们可以把res1理解成模块1,res2理解成模块2,每个模块都有自己独立的数据独立的内存,互不影响,这也是利用了闭包的变量私有化的特性,所以我们看到的打印结果并不是0。

3 柯里化

函数柯里化的核心在于:函数里面返回函数,从而做到参数复用的目的,从函数里面返回函数大家就知道和闭包脱不了干系了是吧。因为我们这个章节主要讲解的是闭包,所以就不去抠柯里化了,给大家提一下,然后大家可以去看看专门讲解柯里化的文章,还是很多的,也写得很好。

四.总结

1 闭包的形成条件

(1)函数嵌套

(2)内嵌函数必须引用外部函数中的变量且内嵌函数必须被return出去

2 闭包的特性

(1)变量私有化

(2)持久性存储

3 闭包的使用场景

(1)防抖

(2)模块化

(3)柯里化

最后

要是文章中又欠妥的地方烦请各位在评论区指出或者私信我,谢谢*

16213079543682966.jpg