JavaScript闭包深入解析:原理、应用与内存管理

作者:代码拾荒时间:2026-10-25 22:54:23浏览:286

本文深入解析JavaScript闭包的定义、产生条件及生命周期。通过代码示例展示闭包在模块封装中的应用,并重点探讨因局部变量未释放导致的内存泄漏问题及其解决方法,帮助开发者理解闭包机制并优化代码性能。

初识闭包

闭包是JavaScript中的核心概念之一,也是面试中的高频考点。本文将系统梳理闭包的相关知识,供读者参考学习。

什么是闭包

闭包本质上是嵌套的内部函数,它引用了外部函数的变量或函数对象。在JavaScript中,只有函数内部的子函数才能访问局部变量,因此闭包可以理解为“定义在函数内部的函数”。从本质上讲,闭包建立了函数内部与外部的连接。需要注意的是,在Chrome浏览器中调试闭包时存在一个细节:新版Chrome需要先调用fun2()并设置debugger,才能正确显示闭包信息。

<script>
    function fn1(){
        var a = 2;
        function fn2(){//执行函数定义就会产生闭包(不用调用内部函数)
            console.log(a);
        }
        //新版的chrome需要返回一下内部函数才会显示闭包
        return fn2()
    }
    fn1()
</script>

初识闭包

如何产生闭包

当嵌套的内部(子)函数引用了外部(父)函数的变量或函数时,闭包即被产生。

<script>
    // 将函数作为另一个函数的返回值
    function fn1(){
        var a = 2;
        function fn2(){//
            a++;
            console.log(a);
        }
        return fn2 //将一个内部函数作为一个外部函数的返回值返回
    }
    var f = fn1()
    //整个过程产生了一个闭包,主要看你产生了几个内部函数对象,调用了几次外部函数
    //闭包的特点就是函数内部的变量会一直存在于内存中,不会立即释放。
    f()//3   这里的f()是调用了内部函数
    f()//4
</script>

初识闭包

<script>
    // 将函数作为实参传递给另一个函数调用
    function showDelay(msg,time){
        //setTimeout 的第一个参数是函数,符合闭包的规则
        setTimeout(function(){
            alert(msg)
        },time)
    }
    showDelay('张三',2000)
</script>

初识闭包

产生闭包条件

产生闭包需要满足两个条件:一是存在函数嵌套结构;二是内部函数引用了外部函数的数据(包括变量或函数)。

闭包的作用

闭包的主要作用有两个:一是使函数内部的变量在函数执行完毕后仍保留在内存中,从而延长局部变量的生命周期;二是允许函数外部对函数内部的数据(变量或函数)进行读写操作。

闭包的生命周期

闭包在嵌套的内部函数定义执行完毕时产生(注意:不是在调用时),而在嵌套的内部函数成为垃圾回收对象时死亡。

<script>
    function fn1 () {
        //此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
        var a = 2;
        function fn2 () {//
            a++;
            console.log(a);
        }
        return fn2 //将一个内部函数作为一个外部函数的返回值返回
    }
    var f = fn1()
    f()//3   
    f()//4
    f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
</script>

闭包的应用

闭包常用于定义JavaScript模块。通过将数据和功能封装在一个函数内部(私有化),仅向外暴露包含多个方法的对象或函数。模块使用者只需通过暴露的对象调用相应方法即可实现功能。

//myModule.js 文件
function myModule(){
    // 私有数据
    var msg = 'My Module'
    function showUpper(){
        console.log('showUpper' +msg.toUpperCase());
    }
    function showLow(){
        console.log('showLow' +msg.toLowerCase());
    }
    //向外暴露对象(给外部使用的方法)
    return {
        showUpper:showUpper,
        showLow:showLow
    }
}
//index.html文件
<script src="./myModule.js"></script>
<script>
    var module = myModule()
    module.showUpper()
    module.showLow()
</script>
 

初识闭包

虽然可以使用匿名函数来实现闭包,从而便捷地调用内部属性,但这可能导致全局变量名污染。相比之下,前文提到的第一种模块模式更为推荐。

//myModule2.js文件
(function(){
    // 私有数据
    var msg = 'My Module'
    // 操作数据的函数
    function showUpper(){
        console.log('showUpper' +msg.toUpperCase());
    }
    function showLow(){
        console.log('showLow' +msg.toLowerCase());
    }
    //向外暴露对象(给外部使用的方法)
    window.myModule2 = {
        showUpper:showUpper,
        showLow:showLow
    }
})()
//index.js文件
<script src="./myModule2.js"></script>
<script>
    myModule2.showUpper()
    myModule2.showLow()
</script

初识闭包

闭包的缺点及解决方法

使用闭包时,函数执行完毕后内部局部变量不会立即释放,导致占用内存时间变长,容易引发内存泄漏。因此,在日常开发中应尽量避免滥用闭包,或对局部变量及时进行释放处理。

<script>
    function fn1(){
        var arr = new Array[100000]
        function fn2(){
            console.log(arr.length);
        }
        return fn2
    }
    var f = fn1()
    f()
    //不用闭包或者回收闭包
    f = null//让内部函数成为垃圾对象 --> 回收闭包
</script>

内存溢出是指程序运行所需内存超过剩余可用内存时抛出的错误。

<script>
    var obj = {}
    for(var i=0;i<10000;i++){
        obj[i]=new Array(1000000)
        console.log('------');
    }
</script>

初识闭包

内存泄漏是指占用的内存未及时释放,积累过多可能导致内存溢出。常见的内存泄漏原因包括:意外的全局变量、未及时清理的计时器或回调函数,以及闭包的使用。

<script>
    //意外的全局变量
    function fn(){
        a = 10;
        console.log(a);
    }
    // 调用函数虽然能打印a,但是a并没有被释放掉。一不注意就设置了一个全局变量
    fn()
    //没有及时清理计时器或回调函数
    var intervalId = setInterval(function(){ //启动循环定时器后不清理
        console.log('--------');
    },2000)
    // clearInterval(intervalId)
    //闭包
    function fn1(){
        var a = 2 //闭包 a 并没有被释放掉
        function fn2(){
            console.log(++a)
        }
        return fn2
    }
    var f = fn1()
    f()
    // f = null 不执行这条语句,a的值一直在
</script>

闭包案例

<script>
    // 案例一:
    var name = "this is Window"
    var object = {
        name:"this is Object",
        getName:function(){
            return function(){
                return this.name
            }
        }
    }
    //闭包的this只能是全局,若在当前作用域中定义了this,就直接使用定义的this,若没定义,则需要一层层向外找,直到全局为止
    //本题是没有闭包的
    alert(object.getName()())//this is Window
    // 案例二:
    var name1 = "this is Window"
    var object1 = {
        name1:"this is Object",
        getName:function(){
            //定义的that形成了闭包,内部函数引用了外部函数的变量,而this指向的是object,所以返回的是object中的name1
            var that = this;
            return function(){
                return that.name1
            }
        }
    }
    alert(object1.getName()())// this is Object
</script>
<script>
    //没有使用闭包的话,数据是没有保留的,所以n传递给o之后,下次运算o值还是上次的值不会发生改变
    function fun(n,o){
        console.log(o);
        return{
            fun:function(m){
                return fun(m,n)
            }
        }
    }
    //在执行fun(0)之后,n被之前的n=0,一直被调用
    var a = fun(0); //闭包里面的n传入了0
    a.fun(1); 
    a.fun(2);
    a.fun(3)//undefined,0,0,0
    //链式执行会导致n的改变,n是前面函数执行的形参
    var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
    //c.fun(2)、c.fun(3)都调用了fun(1)留下的闭包n
    var c = fun(0).fun(1); 
    c.fun(2); 
    c.fun(3)//undefined,0,1,1
</script>

至此,关于JavaScript闭包的深入分析介绍完毕。如需了解更多JS闭包相关内容,欢迎查阅本站往期文章或继续阅读后续文章,感谢大家的支持!

作者最新文章