JavaScript闭包深入解析:原理、应用与内存管理
本文深入解析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闭包相关内容,欢迎查阅本站往期文章或继续阅读后续文章,感谢大家的支持!
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
