你不知道的javascript之闭包

本文详细解析了JavaScript中闭包的概念及其工作原理,探讨了闭包如何帮助函数记住并访问所在词法作用域中的变量,即便函数在不同作用域中执行。此外,还介绍了闭包在实际开发中的应用案例,包括在回调函数、定时器和事件监听器中的使用,并讨论了闭包可能导致的内存泄漏问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闭包的定义

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。

闭包的理解

上述对闭包的描述貌似有点难以理解,不过问题不大,我们通过下面的段代码来理解,就明白是什么意思了。

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

上述代码中,函数 bar() 的词法作用域能够访问 foo() 的内部作用域,因为函数bar引用了函数foo的变量a。然后我们将 bar() 函数本身当作 一个值类型进行传递,当做函数foo()的返回值。

在 foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),执行函数baz()实际上是调用了foo()内部的函数 bar(),这就是定义中的“函数是在当前词法作用域之外执行”,即函数bar()在foo()内部定义,在foo()外部调用。

一般情况下,当一个函数执行完之后,函数内部的作用域会被销毁,js引擎的垃圾回收器会释放函数内的不再使用的内存空间。然而上述代码中,当函数foo()执行完之后,其内部的作用域未被销毁,因为函数bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。闭包使得函数可以继续访问定义时的词法作用域。

闭包的运用

回调函数中的闭包

闭包虽然看似神秘,但是我们在平时的开发中已经或多或少地使用了闭包,只是我们没有发觉而已,比如类似于如下代码:

function wait(message) { 
    setTimeout( function timer() { 
       console.log( message );     
    }, 1000 ); 
 
} 
wait( "Hello, closure!" );

上述代码中,将一个内部函数(名为 timer)传递给 setTimeout(…)。此时timer 具有涵盖 wait(…) 作用域的闭包,因此还保有对wait()的变量 message 的引用。在wait(…) 执行 1000 毫秒后,它的内部作用域并不会被销毁,因为timer 函数依然保有 wait(…) 作用域的闭包。

在定时器、事件监听器、 Ajax 请求等任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包

for循环与闭包

我们来考察一下如下代码:

for (var i=1; i<=5; i++) {     
    setTimeout( function timer() {  
         console.log( i );     
	}, i*1000 ); 
}

正常情况下,我们对上述代码行为的预期是分别输出数字 1~5,每秒一次,每次一个。但实际上,这段代码在运行时会以每秒一次的频率输出五次 6。
首先解释 6 是从哪里来的。这个循环的终止条件是 i 不再 <=5。条件首次成立时 i 的值是 6。因此,输出显示的是循环结束时 i 的最终值。

我们明明执行了五次函数,并且每次都是打印i,按理说i每次都是不同的,但最终结果是每次打印的i都是6,这是为什么呢?一个合理的解释是虽然这五个函数是在各个迭代中分别定义的, 但是它们其实都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。

那么怎么样才能使得在每一次迭代(循环)中,timer函数引用的是不同的i呢?答案是使用闭包。我们改造一下代码:

for (var i=1; i<=5; i++) { 
    let j = i;    
    setTimeout( function timer() {   
       console.log( j );     
    }, j*1000 ); 
}

上述代码中,我们在for循环的作用域中使用let声明了一个变量j保存每一次循环的i(使用let声明变量实际上在for循环内部形成了了一个块级作用域,这是es6中let的特性,不太了解的朋友可以去看看es6),这时timer()函数就引用了for循环作用域中的j,形成闭包。

闭包的缺点

使用闭包可以帮我们解决很多问题,但闭包的缺点是导致内存泄露,因为闭包会一直保持着对某些本该释放的作用域的引用,因而作用域内的变量所占用的内存不会被释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值