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 相关文章推荐
PNG背景在不同浏览器下的应用
Jun 22 Javascript
基于jquery的合并table相同单元格的插件(精简版)
Apr 05 Javascript
JavaScript之引用类型介绍
Aug 10 Javascript
JQuery AJAX 中文乱码问题解决
Jun 05 Javascript
实用jquery操作表单元素的简单代码
Jul 04 Javascript
Angular-Ui-Router+ocLazyLoad动态加载脚本示例
Mar 02 Javascript
react.js 父子组件数据绑定实时通讯的示例代码
Sep 25 Javascript
Angular Material Icon使用详解
Nov 07 Javascript
JS实现获取自定义属性data值的方法示例
Dec 19 Javascript
微信小程序设置全局请求URL及封装wx.request请求操作示例
Apr 02 Javascript
浅析vue插槽和作用域插槽的理解
Apr 22 Javascript
webpack4.0+vue2.0利用批处理生成前端单页或多页应用的方法
Jun 28 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使用GIFEncoder类生成gif动态滚动字幕
2014/07/01 PHP
Yii框架参数化查询中IN查询只能查询一个的解决方法
2017/05/20 PHP
XML+XSL 与 HTML 两种方案的结合
2007/04/22 Javascript
JQuery从头学起第一讲
2010/07/04 Javascript
javascript运动效果实例总结(放大缩小、滑动淡入、滚动)
2016/01/08 Javascript
vue-router 学习快速入门
2017/03/01 Javascript
vue-dialog的弹出层组件
2020/05/25 Javascript
利用node.js如何创建子进程详解
2017/12/09 Javascript
原生JS实现简单的无缝自动轮播效果
2018/09/26 Javascript
Vue触发式全局组件构建的方法
2018/11/28 Javascript
微信小程序按钮点击动画效果的实现
2019/09/04 Javascript
js贪心算法 钱币找零问题代码实例
2019/09/11 Javascript
layui默认选中table的CheckBox复选框方法
2019/09/19 Javascript
vue+render+jsx实现可编辑动态多级表头table的实例代码
2020/04/01 Javascript
[02:39]DOTA2国际邀请赛助威团西雅图第一天
2013/08/08 DOTA
[09:34]2018DOTA2国际邀请赛寻真——永不放弃的iG
2018/08/14 DOTA
在DigitalOcean的服务器上部署flaskblog应用
2015/12/19 Python
Python使用Srapy框架爬虫模拟登陆并抓取知乎内容
2016/07/02 Python
Python 爬虫学习笔记之正则表达式
2016/09/21 Python
numpy使用fromstring创建矩阵的实例
2018/06/15 Python
Django实现学生管理系统
2019/02/26 Python
对pyqt5之menu和action的使用详解
2019/06/20 Python
python GUI库图形界面开发之PyQt5菜单栏控件QMenuBar的详细使用方法与实例
2020/02/28 Python
python中pow函数用法及功能说明
2020/12/04 Python
CSS3教程(4):网页边框和网页文字阴影
2009/04/02 HTML / CSS
美国正版电视节目和电影在线观看:Hulu
2018/05/24 全球购物
COACH德国官方网站:纽约现代奢侈品牌,1941年
2018/06/09 全球购物
好的自荐信的要求
2013/10/30 职场文书
庆元旦文艺演出主持词
2014/03/27 职场文书
公证书样本
2014/04/10 职场文书
兴趣小组活动总结
2014/05/05 职场文书
2014年母亲节寄语
2014/05/07 职场文书
初一新生军训方案
2014/05/22 职场文书
公司年夜饭通知
2015/04/25 职场文书
2016年教师学习廉政准则心得体会
2016/01/20 职场文书
世界十大评分最高的动漫,CLANNAD上榜,第八赚足人们眼泪
2022/03/18 日漫