JavaScript闭包详解


Posted in Javascript onFebruary 02, 2015

在上一篇文章我们对预解释作了概述,在写这篇博文前打算写几个经典案例,考虑到那些案例综合性比较强,也就循序渐进的有了这篇博文,这样对于学习和深入JavaScript也更加容易入手。

一同事去面试,面试官问了一道题:你写一个闭包我看下?于是同事火速写出如下代码:

function fn(){

    alert('Hello JavaScript Closure!!!');//妈蛋,E文本来就不好,找翻译才把闭包单词写出来

}

fn();

然后面试官摇摇头说道:“这怎么能叫闭包呢?”,最终两人争执不下,同事果断走人,面试官什么玩意儿?(本故事纯属虚构,如有雷同纯属巧合)

闭包可能在很多人眼中都是“高大不好上”的技术,可能在很多人眼中只有这样才算得上闭包:

示例1:

function fn() {

    return function () {

        alert('示例1');

    }

}

fn()();

示例1 PS:这个看起来不怎么高级,看样子这人水平不咋地哦!

示例2:

;(function () {

    alert('示例2');

})();

示例2 PS:这个看起来比上一个要高级,而且第一个括号前还加了一个分号,为何加一个分号,好吧我们先把这个疑问留这儿,后面会讲到。

示例3:

~function fn() {

    alert('示例3')

}();

示例3 PS:这个最高级了,简直吊炸天,我读书少,你们别骗我!

撸主读书不多,仅能写出这三种“闭包”,相信博友们能写出更多更优秀的“闭包”;到此请先暂停我的瞎掰,接下来研究下函数运行的机制,貌似有人已经知道了,肯定是作用域,我真的很不想在标题上再加上这个作用域,这样总感觉差点儿意思,这个几个东西本来都是一起的,为何要重复呢?老习惯,先上代码:

var n = 10;

function fn(){

    alert(n);

    var n = 9;

    alert(n);

}

fn();

好简单的说,我们画图(撸主只会用Windows自带的画图软件,若有更好的请博友推荐)来分析下:

JavaScript闭包详解

分析1

    从图中我们看到了两个作用域,一个是window作用域(顶级作用域),一个是fn调用的时候形成的一个私有作用域;那什么是作用域,作用域其实就是代码执行的环境。举个栗子,一个学生他的学习环境是学校,相当于他的作用域是学校,假如这个学生很调皮,晚上经常FanQiang去网吧打游戏,相当于形成了一个私有环境,这个作用域就是网吧。好吧!这个栗子太TM像撸主本人了,不由感叹一句:“少壮不努力,长大干挨踢”。还是回到正题,其实函数fn的定义就是指向一段代码的描述(图中红框),当这个fn调用(图中的绿框)的时候,就会形成一个作用域,当然这个作用域中的代码执行前也会预解释,我是不会告诉你这个作用域是当它执行完毕后会被销毁,这个fn再次调用也会形成一个新的作用域,然后执行前预解释,然后代码执行,最后执行完毕销毁。

理解闭包

    我们知道函数被调用在执行的时候会形成一个私有作用域(执行环境),这个私有作用域就是闭包。回头再看看闭包还是传说中的“高大不好上”吗?我们再回头看看第一个面试故事,还有我写的三个示例,它们其实都是闭包,确切的说那三个示例都是闭包的常用形式。

应用场景

现在有这样一个需求:HTML页面中有一个ul标签,ul下面有5个li标签,要求任意点击一个li,弹出被点击的这个li所在的索引(索引从0开始)位置,HTML结构如下:

<ul id="ul">

    <li>列表1</li>

    <li>列表2</li>

    <li>列表3</li>

    <li>列表4</li>

    <li>列表5</li>

</ul>

机智的我火速写出如下代码:
var lis = document.getElementById('ul').getElementsByTagName('li');

for (var i = 0, len = lis.length; i < len; i++) {

    lis[i].onclick = function () {

        alert(i);

    };

}

最终测试,看是否完美实现这个需求:

JavaScript闭包详解

发现无论点击多少次,最终都弹出这个结果,而需求期望的结果是:点击列表1弹出0,点击列表2弹出1,点击列表3弹出2……此时此刻只想用这幅图来形容现在的心情:

JavaScript闭包详解

(当原型在演示时没能按设计的要求运行时的样子)

这可如何才好,为何总是弹出5呢?理论上很正确呀!我们不妨画图来分析下:

JavaScript闭包详解

其实我们只是给每一个li的onclick其实就是保存的一段函数的描述字符串,这个字符串内容就是上图红框中的内容,如果您还是不信,我有图有真相:

JavaScript闭包详解

在Chrome控制台下输入:lis[4].onclick,其值就是函数的描述。当我们在点击第5个列表时,其实就是相当于lis[4].onclick(),调用了这段函数描述,我们知道函数在被调用执行的时会形成一个私有作用域,在这个私有作用域下也是先预解释,然后代码执行,此时会去找i,在当前私有作用域下没有i,然后去window作用域下找到了i,因此每次点击都弹出5。

显然上面的代码无法满足这个需求,我们代码那么写是不正确的,我们思考一下出现问题的原因是什么?其实原因就是每次点击的时候都是读取的window下的i,此时这个i的值已经是5了,于是有了如下代码:

方式一:

var lis = document.getElementById('ul').getElementsByTagName('li');

function fn(i) {

    return function () {

        alert(i);

    }

}

for (var i = 0, len = lis.length; i < len; i++) {

    lis[i].onclick = fn(i);

}

方式二:

var lis = document.getElementById('ul').getElementsByTagName('li');

 

for (var i = 0, len = lis.length; i < len; i++) {

    ;(function (i) {

        lis[i].onclick = function () {

            alert(i);

        };

    })(i);

}

方式三:

var lis = document.getElementById('ul').getElementsByTagName('li');

 

for (var i = 0, len = lis.length; i < len; i++) {

    lis[i].onclick = function fn(i) {

        return function () {

            alert(i);

        }

    }(i);

}

一口气写了三种方式,其思想都是一样的,就是将这个变量i用一个私有变量存储起来,这里我就只讲方式二,当然明白其中一个其余也就都明白了。按照惯例,我们画图来一步步分析下:

JavaScript闭包详解

我详细的对整个代码执行做了描述,需要注意的是:每个li的onclick属性都要占用(function(i){ … })(i)作用域,当这个函数执行完毕后不会被销毁,因为它被外面的li(这个li是window作用域下的)占用着,因此这个作用域不会被销毁。当点击任意一个li时,function(){ alert(i); }会被执行,也会形成一个作用域,这个作用域没有i,它会去(function(){ … })(i)作用域找i,最终在形参找到i,这个形参i的值就是for循环时传进去的;这个例子巧妙地使用闭包来贮存值,完美解决问题。

PS:刚刚说(function(i){ … })(i)为什么在前面加一个分号,其原因就是防止前面的语句忘记加分号,这样导致JavaScript在解析时出错,仅此而已。当然上面的一个应用场景就是Tabs实现原理,可以有其他实现方式,比如自定义属性方式、通过DOM节点关系找到索引,而撸主采用这样一种方式只是为了加深对闭包的理解。

总结

    闭包并不是传说中的高大不好上,其核心就是理解函数定义、调用,函数调用时会形成一个新的私有作用域,当某个作用域被外面占用,那么这个作用域将不会被销毁。撸主读书甚少,有说得不对的地方请博友们指正,同时也感谢大家对撸主文章的支持。

Javascript 相关文章推荐
javascript 学习笔记(六)浏览器类型及版本信息检测代码
Apr 08 Javascript
Jquery中显示隐藏的实现代码分析
Jul 26 Javascript
Query中click(),bind(),live(),delegate()的区别
Nov 19 Javascript
JS嵌套函数调用上下文的问题解决
Mar 26 Javascript
JS中call/apply、arguments、undefined/null方法详解
Feb 15 Javascript
Mongoose实现虚拟字段查询的方法详解
Aug 15 Javascript
利用js编写网页进度条效果
Oct 08 Javascript
浅谈vue2 单页面如何设置网页title
Nov 08 Javascript
jQuery 防止相同的事件快速重复触发方法
Feb 08 jQuery
关于js对textarea换行符的处理方法浅析
Aug 03 Javascript
详解vue axios用post提交的数据格式
Aug 07 Javascript
JS简单表单验证功能完整示例
Jan 26 Javascript
js实现浏览器窗口大小被改变时触发事件的方法
Feb 02 #Javascript
javascript的switch用法注意事项分析
Feb 02 #Javascript
jQuery实现长按按钮触发事件的方法
Feb 02 #Javascript
jQuery实现跟随鼠标运动图层效果的方法
Feb 02 #Javascript
JavaScript针对网页节点的增删改查用法实例
Feb 02 #Javascript
jQuery通过控制节点实现仅在前台通过get方法完成参数传递
Feb 02 #Javascript
jQuery循环动画与获取组件尺寸的方法
Feb 02 #Javascript
You might like
php 代码优化的42条建议 推荐
2009/09/25 PHP
php垃圾代码优化操作代码
2010/08/05 PHP
PHP使用DOMDocument类生成HTML实例(包含常见标签元素)
2014/06/25 PHP
在WordPress的后台中添加顶级菜单和子菜单的函数详解
2016/01/11 PHP
PHP+MySQL实现模糊查询员工信息功能示例
2018/06/01 PHP
php的单例模式及应用场景详解
2021/02/27 PHP
PHP安装扩展mcrypt以及相关依赖项深入讲解
2021/03/04 PHP
使用jQuery向asp.net Mvc传递复杂json数据-ModelBinder篇
2010/05/07 Javascript
jquery获取对象的方法足以应付常见的各种类型的对象
2014/05/14 Javascript
jQuery中bind()方法用法实例
2015/01/19 Javascript
Javascript中的几种URL编码方法比较
2015/01/23 Javascript
Jquery中CSS选择器用法分析
2015/02/10 Javascript
JavaScript跨浏览器获取页面中相同class节点的方法
2015/03/03 Javascript
使用jquery制作弹出框效果
2015/04/03 Javascript
如何用js实现鼠标向上滚动时浮动导航
2016/07/18 Javascript
JS两种类型的表单提交方法实例分析
2016/11/28 Javascript
bootstrap警告框使用方法解析
2017/01/13 Javascript
js实现登录框鼠标拖拽效果
2017/03/09 Javascript
详解微信小程序设置底部导航栏目方法
2017/06/29 Javascript
js 倒计时(高效率服务器时间同步)
2017/09/12 Javascript
JS实现碰撞检测的方法分析
2018/01/19 Javascript
简述vue状态管理模式之vuex
2018/08/29 Javascript
JS与jQuery判断文本框还剩多少字符可以输入的方法
2018/09/01 jQuery
Vue.js 图标选择组件实践详解
2018/12/03 Javascript
Vuejs学习笔记之使用指令v-model完成表单的数据双向绑定
2019/04/29 Javascript
bootstrap+spring boot实现面包屑导航功能(前端代码)
2019/10/09 Javascript
JavaScript 接口原理与用法实例详解
2020/05/12 Javascript
bootstrapValidator表单校验、更改状态、新增、移除校验字段的实例代码
2020/05/19 Javascript
[49:20]2014 DOTA2国际邀请赛中国区预选赛5.21 CIS VS TongFu
2014/05/22 DOTA
[02:05:03]完美世界DOTA2联赛循环赛 LBZS VS Matador BO2 10.28
2020/10/28 DOTA
python+splinter实现12306网站刷票并自动购票流程
2018/09/25 Python
python3用PIL把图片转换为RGB图片的实例
2019/07/04 Python
Django自定义模板过滤器和标签的实现方法
2019/08/21 Python
python生成随机红包的实例写法
2019/09/02 Python
使用 Python 遍历目录树的方法
2020/02/29 Python
音乐幼师求职信
2014/07/09 职场文书