Javascript闭包的作用和示例

闭包是JavaScript最重要的特性之一,也是全栈/前端/JS面试的考点。
那闭包究竟该如何理解呢?

闭包函数的判断和作用

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
那如何判断函数是一个闭包呢?接下来我会配合一些具体的例子来对闭包问题做讲解。
首先问下大家,这个G函数是否是一个闭包呢?

 

Copy

const F = function A(){ return function B(){ return function C(){ return function D(){ var a = 1; return a++ } } } } const G = F()()(); for(var i=0;i<10;i++){ console.log(G()) }

一看就是不是对吧,在这里面的G函数一看就是D函数,只不过长得比较怪而已。
如果是闭包函数那应该长成这样

 

Copy

const F = function A(){ var a = 1; return function B(){ return function C(){ return function D(){ return a++ } } } } const G = F()()(); for(var i=0;i<10;i++){ console.log(G()) }

运行效果如下:

主要区别是这个变量a的声明位置。如果a是在A中声明的,那G就构成了闭包。也就是在G的作用域内,会形成一个名为closure作用域的子域。

那接下来第二个问题来了,这个a存在内存中的哪个位置呢?

在MDN中对JavaScript的定义是这样的

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

好家伙,看起来就很迷。

当定义形式难以理解的时候,我们需要语义,这也说明了一件事,我们需要调试器!
进入调试器后,一切就都明朗了起来。

我们清楚地看到,当脚本运行到 D的内部时,这个Scope也就是作用域里面包含了,Local作用域,Closure作用域和Script以及Global作用域。
Local不用说了,肯定就是函数外的对象,在这里应该是window对象。
那Closure自然就是闭包作用域了。

我们依次运行时,可以清晰地看到,closure作用域内的a在不断增加。

那第三个问题来了。

 

Copy

const F = function A(){ var a = 1; return function B(){ return function C(){ return function D(){ var a = 2; return a++ } } } } const G = F()()(); for(var i=0;i<10;i++){ console.log(G()) }

这里的G是闭包函数吗?

答案肯定不是,因为G已经能在D中找到 a变量了,那就不需要A再提供给他了,因此我们在调试器中也看不到Closure了。

我们在这里可以看到,根本没有了之前的Closure了。

现在第四个问题来了,这个程序的运行结果是什么?

 

Copy

const F = function A(){ var a = 1; return function B(){ return function C(){ var a = 2; return function D(){ return a++ } } } } const G = F()()(); for(var i=0;i<10;i++){ console.log(G()) }

这个是从2开始打印的,而非从1开始打印。
看到这,大家应该对闭包的优先级有认识,闭包也是离得越近优先级越高。

现在第五个问题来了,这个程序中,G的scope作用域里存在几个闭包?

 

Copy

const F = function A(){ var b = 1; return function B(){ var c = 3; return function C(){ var a = 2; return function D(){ b,c return a++ } } } } const G = F()()(); for(var i=0;i<10;i++){ console.log(G()) }

答案是3个,为什么?这里有两个角度可以解释

  1. bca在D中都没有定义,之鞥能从A,B,C中找到abc,所以这里存在三个闭包。
  2. 直接看调试器就知道啦


在调试器中我们能清楚地看到,这里有三个闭包。不解释!

闭包函数的示例

1.计数功能#

在闭包函数的应用中,有很多,这里举个最常见的计数器的例子。

 

Copy

<html> <head></head> <body> <script> var A = (function B(){ return function C(){ var b = 0; return function D(){ debugger return ++b; } } })() var E = A(); var F = A(); </script> <button onclick="console.log('E='+ E())">E++</button> <button onclick="console.warn('F='+ F())">F++</button> </body> </html>

打开后运行效果如下:


点击E++和F++后的效果

在上面的例子中我们发现,我可以用一个类似面向对象的方法,去实现计数功能。

2.setTimeout#

原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。

 

Copy

function func1(a) { function func2() { console.log(a); } return func2; } var fun = func(1); setTimeout(fun,1000);//一秒之后打印出1

3.回调#

定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
比如下面这段代码:当点击数字时,字体也会变成相应的大小。

 

Copy

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试</title> </head> <body> <a href="#" id="size-12">12</a> <a href="#" id="size-20">20</a> <a href="#" id="size-30">30</a> <script type="text/javascript"> function changeSize(size){ return function(){ document.body.style.fontSize = size + 'px'; }; } var size12 = changeSize(12); var size14 = changeSize(20); var size16 = changeSize(30); document.getElementById('size-12').onclick = size12; document.getElementById('size-20').onclick = size14; document.getElementById('size-30').onclick = size16; </script> </body> </html>

4.函数防抖#

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
实现的关键就在于setTimeOut这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。
如下代码所示:

 

Copy

/* * fn [function] 需要防抖的函数 * delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //借助闭包 return function() { if(timer){ clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时 timer = setTimeOut(fn,delay) }else{ timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时 } } }

总之闭包的用处很多,而且很广泛。
希望这篇文章可以对大家能有所帮助!

参考资料:小海鲸 https://www.xiao-haijing.com

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值