闭包就好像从JavaScript中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人才能到达那里。——《你不知道的JavaScript 上卷》

1、起源

js闭包很长一段时间里都是让我头疼的一个东西。工作中遇到类似这样的代码就很怕:

需求

页面内三个按钮,点击按钮控制台输出按钮在所有按钮中的序号,序号从1开始

说明

当然,实际的应用中我们一般不会有这么单纯的需求,也不会写这么刻意的代码,这里我们为了学习,强行挖个坑,自己再填坑。

上代码

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>闭包</title>
</head>
<body>点击显示按钮序号<br><button>click</button><button>click</button><button>click</button><script>var btns = document.querySelectorAll("button");for(var i=0;i<btns.length;i++){btns[i].onclick = function(){console.log(i+1);}}</script>
</body>
</html>
复制代码

不懂闭包前,我就觉得,这很优雅啊,按钮在集合btns中的索引+1正好就是满足需求的。兴奋地赶紧自测,咔咔咔连点三下。

结果

当时内心表情大概就像上面这个哥们。但还是在工位上故作镇定地赶紧百度了下。

修正

百度和修改后的for循环就变成了这个样子,用一个闭包存一下数组索引i的值传给闭包内的函数。

for(var i=0;i<btns.length;i++){btns[i].onclick = (function(tmpI){return function(){console.log(tmpI+1);} })(i);
}
复制代码

click,click,click。大功告成!

2、原理

工作中满足了当时的需求也就立马沉迷代码撸下一个功能去了。但对闭包的详细原理知之甚少,相关问题稍微发生点变化,就又可能让自己云里雾里。近来专门找了几本书,刻意攻克了下,才算开始了解了闭包。

如上问题的解析

代码再贴一下:

var btns = document.querySelectorAll("button");
for(var i=0;i<btns.length;i++){btns[i].onclick = function(){console.log(i+1);}
}
复制代码

上面代码不能按需实现功能的问题在于: 1、for循环内只是将匿名函数引用赋给onclick方法,在函数被调用时才会去实时取i的值 2、i为全局变量,在onclick方法触发时,i的值已被i++的操作变成了4

for(var i=0;i<btns.length;i++){btns[i].onclick = (function(tmpI){return function(){console.log(tmpI+1);} })(i);
}
复制代码

上述代码,onclick方法获得的匿名函数通过立即执行函数返回,立即执行函数内又通过传参的方式,将i的值传入,用tmpI变量保存起来。确保了外部的i++操作不会影响函数内的tmpI的值的变化,就解决了问题。

闭包定义

"闭包(closure)是一个函数在创建时允许自身函数访问并操作该自身函数之外的变量时所创建的作用域。"——《JavaScript 忍者秘籍》

这是我觉得相对来说比较易懂的对闭包的解释。关键要素有两个: 1、函数创建时产生。 2、允许自身访问并操作函数之外的变量。 上述代码return的匿名函数中访问了其函数之外的tmpI变量,形成了闭包。

按书中定义,如下我们大家每天都在写的代码也属于闭包。

var outerValue = "外部变量";function outerFunction(){console.log("outerValue",outerValue);
}
outerFunction();
复制代码

按定义,outerFunction确实访问了其外的outerValue值,属于闭包。但因为函数定义和外部变量都处在全局作用域中,该作用域从未消失过,所以我们也没觉得这有什么特殊的地方。通过实际的chrome调试发现,chrome也不会标记此类的闭包。以下,我们和chrome保持一致,不再特别说明全局作用域闭包。

再举个栗子

var outerValue ="外部变量";
var later;function outerFunction(){var innerValue = "内部值";function innerFunction(paramValue){console.log(outerValue,"Inner can see the outerValue.");console.log(innerValue,"Inner can see the innerValue.");console.log(paramValue,"Inner can see the paramValue.");console.log(tooLate,"Inner can see the tooLate.");}later = innerFunction;
}console.log(tooLate,"Outer can't see the tooLate.");
var tooLate = "outerFunction之后声明的变量";outerFunction();
later("later参数");
复制代码

输出结果如下:

上述例子引用自《JavaScript 忍者秘籍》,笔者做了部分修改。这里innerFunction函数创建时形成了闭包,其访问了outerFunction中的innerValue。 其余部分的代码书中是为了说明闭包的三种更有趣的性质。
1、内部函数的参数是包含在闭包中的。(这是显而易见的)
2、作用域之外的所有变量,即便是函数声明之后的那些声明,也都包含在闭包中。(在调用later时可以访问到tooLate变量)
3、相同的作用域内,尚未声明的变量不能进行提前引用。(如代码中先打印的tooLate为undefined,我觉得这也是显而易见的。)

chrome中对outerFunction闭包的标识如下:

在调用outerFunction,定义innerFunction时,访问了innerValue,形成了outerFunction闭包。

3、延伸

按我的理解,js有闭包的概念是因为js设计之初没有块级作用域,只能通过函数来限制变量的有效范围。当有了块级作用域,其实也就不再需要写闭包。如开头例子如果用ES6 let改写,也可实现需求功能。

var btns = document.querySelectorAll("button");
for(let i=0;i<btns.length;i++){btns[i].onclick = function(){console.log(i+1);}
}
复制代码

读者可以试下,这也是满足需求的。这是因为使用let定义i,ES6做了处理,每次循环的i都有各自的作用域,其值不会互相影响,函数调用时,其仍然保持了原值。

文中内容有错误,或读者对此仍有疑惑的,欢迎大家在评论中留言或者加微信一起探讨学习。

主要参考资料

《你不知道的JavaScript 上卷》——[美]Kyle Simpson
《JavaScript 忍者秘籍》——[美]John Resig,Bear Bibeault

转载于:https://juejin.im/post/5ce12c285188254081056efa

JavaScript:从此不再怕闭包相关推荐

  1. android系统刷机,安卓手机刷机完全攻略 从此不再怕刷机

    安卓手机刷机一直是是个比较热门的话题,有句话叫做了安卓用户有三好刷机.重启.扣电池,冬天能当暖手宝.这也恰恰说明了刷机对于安卓手机来说,就跟吃饭喝水一样再简单不过了.很多小白童鞋在刷机的时候,都害怕手 ...

  2. JavaScript内存泄露,闭包内存泄露如何解决

    转载于:JavaScript内存泄露,闭包内存泄露如何解决 - 一粒一世界 - 博客园 JavaScript 内存泄露的4种方式及如何避免 简介 什么是内存泄露? JavaScript 内存管理 Ja ...

  3. javascript函数作用域与闭包

    8.8. 函数作用域与闭包        如第四章所述,JavaScript函数的函数体在局部作用域中执行,局部作用域不同于全局作用域.本章将解释这些内容和相关的作用域问题,包括闭包.[*] [*] ...

  4. 干货!最全需求评审指南,让你不再怕被怼

    本文由作者 冰冰酱 发布于社区 对于产品新人而言,日常最头疼的会议就是需求评审. 在做产品的这几年,笔者开过上百场需求评审会,曾经被研发在会上怼哭过一次,也遇到过研发和产品大吵半小时.最终有一方摔门而 ...

  5. PHP 依赖注入,从此不再考虑加载顺序

    说这个话题之前先讲一个比较高端的思想--'依赖倒置原则' "依赖倒置是一种软件设计思想,在传统软件中,上层代码依赖于下层代码,当下层代码有所改动时,上层代码也要相应进行改动,因此维护成本较高 ...

  6. 用好知晓云,从此不再「网抑云」

    近期在多个社交平台上,都能看到一些打着「网抑云」标签的视频或文章,引起大家的关注. 「网抑云」最开始来自网友对音乐 APP 里的矫情式评论的调侃,是指到夜深时刻,在我们最敏感的时候,打开音乐 APP ...

  7. 让你实现财富自由,从此不再缺资金

    [提额实操见证].[刷卡秘籍].[任意一家银行提额方法] 1.14家银行提额方法 2.信用卡办卡标准,办大额度信用卡技巧 3.解析提额失败的原因,并制定解决方案 4.科学用卡,教你优化卡片 5.解析信 ...

  8. edittext 软键盘上方_Android 软键盘的全面解析,让你不再怕控件被遮盖

    原标题:Android 软键盘的全面解析,让你不再怕控件被遮盖 作者 | Vander丶 编辑 | 苏宓 微信公众号 | mobilehub 背景 Android软键盘这块从我入职到现在,是一个一直纠 ...

  9. 断供?我们不再怕!DTAS 3D-自主可控国产三维公差分析软件重装上阵!

    背景介绍 计算机虚拟仿真分析是产品正向开发的重要组成部分,而公差分析是其中至关重要的一环.随着中国制造向智能制造的转变,在制造业数字化转型浪潮的推动下,计算机辅助公差设计(Computer-Aided ...

最新文章

  1. 物联网是地产行业转型的有力推手
  2. linux中select()函数分析
  3. Lock锁的基本使用
  4. pta函数统计素数并求和_关于求和的4种函数公式,此文讲透了,尤其是第4种,绝对的高效...
  5. 微信小程序view动态长度_微信小程序实现动态获取元素宽高的方法分析
  6. opencv运动目标跟踪预测_基于Opencv的运动目标的检测和跟踪.pdf
  7. 开源三维地球_用开源拯救地球
  8. 【faebdc的模拟赛】T1错位
  9. Mysql学习总结(65)——项目实战中常用SQL实践总结
  10. ThreadLocal可以解决并发问题吗?
  11. 圆与平面的接触面积_如果把绝对圆的球体放在绝对平的平面上,那接触面是不是无限小?...
  12. 如何使用MOQ进行单元测试
  13. 直播预告:摄影测量影像快速立体匹配关键技术研究
  14. 魔兽世界服务端linux,LightsHope/vMangos – 基于Ubuntu服务器的WOW私服搭建教程
  15. Helix QAC 2021.1
  16. sql中字符串转换成日期
  17. 论文笔记-Monocular Depth Estimation as Regression of Classification using Piled Residual Networks
  18. MarkDown 高级操作
  19. 热爱3D游戏建模,副业兼职外包可以做到什么程度?
  20. 大数据学习计划【2019经典不断更新】

热门文章

  1. qchart 坐标轴设置_实战PyQt5: 156-QChart图表之更换图表主题
  2. python3 isinstance用法_对python中assert、isinstance的用法详解
  3. 远程管理卡介绍使用场景
  4. java图片16帧动画_Java实现帧动画的实例代码
  5. mysql大表join小表速度很慢_mysql多表join中,为什么子查询会那么慢,怎么解决-问答-阿里云开发者社区-阿里云...
  6. python模拟用户压力测试_Python 工具 Locust 进行负载测试
  7. QT键盘响应卡顿的解决方法
  8. 【项目管理】上线切割计划实践
  9. 信息系统项目管理师:第7章:项目成本管理(1)-章节重点
  10. Leaflet中使用awesome-markers插件显示带图标的marker