JS 探索闭包

1. 看见闭包

在定义闭包之前,我们先写一个简单的闭包样例,并通过chrome debug工具尝试看到闭包。

例1

function fn1(){
    var a = 1;
    function fn2(){
        var b = 2;
        debugger;
        console.log(a, b); 
    }
    fn2();
}
fn1();

在右侧工具栏中的Scope窗口中可以看到当前函数fn2的作用域链。fn2内调用了两个变量 a 和 b,其中 fn2 定义的 b 在Local中,而 fn1 定义的 a 则在Closure(fn1)中。Closure即为闭包的英文。

在这里插入图片描述

图1 fn2的作用域链

从上面的例子中可能对闭包有了些认识,下面再尝试通过几个例子,更加深入了解闭包。

例2

function fn1(){
    var a = 1;
    function fn2(){
        var b = 2;
        function fn3() {
            var c = 3;
            debugger;
            console.log(a, b, c);
        }
        fn3();
    }
    fn2();
}
fn1()

当内部函数fn3同时调用其外部函数fn2中的b 以及最外部函数fn1时,将出现两个闭包。

在这里插入图片描述

图2 fn3的作用域链

例3

function fn1(){
    var a = 1;
    function fn2(){
        var b = 2;
        function fn3() {
            var c = 3;
            debugger;
            //console.log(a, b, c);
            console.log(a, c);
        }
        fn3();
    }
    fn2();
}
fn1()

当内部函数fn3不再调用外部函数fn2中的变量时,fn2将在作用域链中消失。同理,如果不再引用fn1内的变量时,fn1也会在作用域链中消失。

在这里插入图片描述

图2 fn2从fn3的作用域链中消失

2. 定义闭包

2.1 闭包

​ 从chrome的展示中,我们可以尝试对闭包进行下定义——闭包:当内层函数调用外层函数中的变量时,外层函数的作用域(变量对象)在内层函数的作用域链中称为闭包。

2.2 闭包的定义

​ 闭包没有一个标准定义,所以在不同地方对闭包的定义是不同的,上面这个定义仅作参考。

  • 在《JavaScript高级程序设计(第3版)》中,对闭包的定义是“闭包是指有权访问另一个函数作用域中的变量的函数。”。即认为闭包是样例中的内部函数。

  • 在《深入理解JavaScript》中,对闭包的定义是“函数以及它所连接的周围作用域中的变量即为闭包。”。即认为内部函数以及调用的外部函数的变量共同组成闭包。

  • 在《javascript权威指南中文第六版》中,对闭包的定义是“函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为‘闭包’。”。该定义写得相对笼统,但也可能是相对正确的一版。可以理解为——闭包是一种引用关系,引用方是内部函数,被引用方是外部函数的变量。

2.3 闭包三要素

​ 虽然定义各不相同,但基本内涵是一致的,我们可以从中总结出闭包三要素。

  • 函数嵌套
  • 内部函数引用外部函数的变量
  • 内部函数和外部函数都要被调用

2.4 闭包的生命周期

  • 产生:外部函数被调用时。注:内部函数并不需要被调用或是引用,函数在定义时,作用域链就已经确定了,闭包已经形成。但由于内部函数没有被调用,闭包刚产生就死亡。
  • 死亡:内部函数被浏览器作为垃圾回收时,即没有再调用或是被其他变量引用。

3. 使用闭包

3.1 闭包的作用

  • 延长外部函数变量的生命周期。

  • 可以隐藏一些属性和方法,仅暴露一个接口供他人使用。在一些情况下,函数功能的实现需要借助一些外部变量,但不想这些变量直接暴露在全局作用域中,就可以借助闭包实现。

3.2 闭包的常用方式

  • 作为立即执行函数(IIFE)的返回值

    var fn = (function(){
        var value = 1;
        return function(){
            console.log(value++);
        }
    })();
    
    fn();
    

    正常情况下,IIFE执行完毕,value会被垃圾回收,但由于引用value的内部匿名函数被变量fn引用着,所以value不会被垃圾回收。即延长了value的生命周期。

  • 模块化

    在开发中,实现不同的功能模块时,极有可能出现变量名或是函数名冲突的情况。

    <body>
        <header><input value="请输入搜索内容"></header>
        <footer><input value="请留下联系方式"></footer>
        <script>
            function headerModule() {
                var iptNode = document.querySelector('header input');
                
    
                function show() {
                    console.log('这是头部模块');
                }
                return {
                    iptNode : iptNode,
                    show : show
                }
            }
    
            function footerModule(){
                var iptNode = document.querySelector('footer input');
    
                function show() {
                    console.log('这是脚部模块');
                }
                return {
                    iptNode : iptNode,
                    show : show
                }
            }
    
            var header = headerModule();
            var footer = footerModule();
    
            console.log(header.iptNode.value);
            console.log(footer.iptNode.value);
    
            header.show();
            footer.show();
        </script>
    </body>
    

4. 闭包的缺点

由于闭包机制的存在,使得外部函数的变量无法正常释放,若滥用闭包可能会出现以下情况

  • 内存泄漏:外部函数的变量无法正常释放。
  • 内存溢出:内存被延长了生命周期外部函数的变量占满。

解决方案:当内部函数不再需要被调用和引用时,将引用内部函数的变量置为null。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值