理解和运用JavaScript的闭包机制


Posted in Javascript onAugust 13, 2015

伟大的爱因斯坦同志说过:“如果你无法向一个 6 岁小孩解释清楚某问题,那说明你自己都没整明白”。然而,当我向一个 27 岁的朋友解释什么是闭包时,却彻底失败了。

这原本是国外某哥们儿在 Stack Overflow 上对 JavaScript 闭包所提出的问题。不过既然此问题是在 Stack Overflow 提出的,当然也会有很多高手出来解答,其中有些回答确实是经典,如下面这个:

如果在一个外部函数中再定义一个内部函数,即函数嵌套函数,那么内部函数也可以访问外部函数中的变量:

function foo(x) {
 var tmp = 3;
 function bar(y) {
 alert(x + y + (++tmp));
 }
 bar(10);
}

foo(2); // alert 16
foo(2); // alert 16
foo(2); // alert 16

此段代码可以正确执行,并返回结果:16,因为 bar 能访问外部函数的变量 tmp, 同时也能访问外部函数 foo 的参数 x。但以上示例不是闭包!

要实现闭包的话,需要将内部函数作为外部函数的返回值返回,内部函数在返回前,会将所有已访问过的外部函数中的变量在内存中锁定,也就是说,这些变量将常驻 bar 的内存中,不会被垃圾回收器回收,如下:

function foo(x) {
 var tmp = 3;
 return function (y) {
 alert(x + y + (++tmp));
 }
}
var bar = foo(2); // bar 现在是个闭包了
bar(10); // alert 16
bar(10); // alert 17
bar(10); // alert 18

上述代码中,第一次执行 bar 时,仍会返回结果:16,因为 bar 仍然可以访问 x 及 tmp,尽管它已经不直接存在于 foo 的作用域内。那么既然 tmp 被锁定在 bar 的闭包里,那么每次执行 bar 的时候,tmp 都会自增一次,所以第二次和第三次执行 bar 时,分别返回 17 和 18。

此示例中,x 仅仅是个纯粹的数值,当 foo 被调用时,数值 x 就会作为参数被拷贝至 foo 内。

但是 JavaScript 处理对象的时候,使用的总是引用,如果用一个对象作为参数来调用 foo,那么 foo 中传入的实际上是原始对象的引用,所以这个原始对象也相当于被闭包了,如下:

function foo(x) {
 var tmp = 3;
 return function (y) {
 alert(x + y + tmp++);
 x.memb = x.memb ? x.memb + 1 : 1;
 alert(x.memb);
 }
}
var age = new Number(2);
var bar = foo(age); // bar 现在是个闭包了
bar(10); // alert 15 1
bar(10); // alert 16 2
bar(10); // alert 17 3

和期望的一样,每次执行 bar(10) 时,不但 tmp 自增了,x.memb 也自增了,因为函数体内的 x 和函数体外的 age 引用的是同一个对象。

via http://stackoverflow.com/questions/111102/how-do-javascript-closures-work

补充:通过以上示例,应该能比较清楚的理解闭包了。如果觉得自己理解了,可以试着猜猜下面这段代码的执行结果:

function foo(x) {
 var tmp = 3;
 return function (y) {
 alert(x + y + tmp++);
 x.memb = x.memb ? x.memb + 1 : 1;
 alert(x.memb);
 }
}
var age = new Number(2);
var bar1 = foo(age); // bar1 现在是个闭包了
bar1(10); // alert 15 1
bar1(10); // alert 16 2
bar1(10); // alert 17 3

var bar2 = foo(age); // bar2 现在也是个闭包了
bar2(10); // alert ? ?
bar2(10); // alert ? ?
bar2(10); // alert ? ?

bar1(10); // alert ? ?
bar1(10); // alert ? ?
bar1(10); // alert ? ?

实际使用的时候,闭包可以创建出非常优雅的设计,允许对funarg上定义的多种计算方式进行定制。如下就是数组排序的例子,它接受一个排序条件函数作为参数:

[1, 2, 3].sort(function (a, b) {
 ... // 排序条件
});

同样的例子还有,数组的map方法是根据函数中定义的条件将原数组映射到一个新的数组中:

[1, 2, 3].map(function (element) {
 return element * 2;
}); // [2, 4, 6]

使用函数式参数,可以很方便的实现一个搜索方法,并且可以支持无限制的搜索条件:

someCollection.find(function (element) {
 return element.someProperty == 'searchCondition';
});

还有应用函数,比如常见的forEach方法,将函数应用到每个数组元素:

[1, 2, 3].forEach(function (element) {
 if (element % 2 != 0) {
  alert(element);
 }
}); // 1, 3

顺便提下,函数对象的 apply 和 call方法,在函数式编程中也可以用作应用函数。 这里,我们将它们看作是应用函数 —— 应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):

(function () {
 alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

闭包还有另外一个非常重要的应用 —— 延迟调用:

var a = 10;
setTimeout(function () {
 alert(a); // 10, after one second
}, 1000);
还有回调函数:

//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
 // 当数据就绪的时候,才会调用;
 // 这里,不论是在哪个上下文中创建
 // 此时变量“x”的值已经存在了
 alert(x); // 10
};
//...

还可以创建封装的作用域来隐藏辅助对象:

var foo = {};

// 初始化
(function (object) {

 var x = 10;

 object.getX = function _getX() {
  return x;
 };

})(foo);

alert(foo.getX()); // 获得闭包 "x" ? 10
Javascript 相关文章推荐
ExtJS4 表格的嵌套 rowExpander应用
May 02 Javascript
javascript文本框内输入文字倒计数的方法
Feb 24 Javascript
jQuery实现新消息在网页标题闪烁提示
Jun 23 Javascript
JS表格组件神器bootstrap table详解(强化版)
May 26 Javascript
jQuery实现拖拽可编辑模块功能代码
Jan 12 Javascript
js实现简易垂直滚动条
Feb 22 Javascript
ES6字符串模板,剩余参数,默认参数功能与用法示例
Apr 06 Javascript
微信小程序 chooseImage选择图片或者拍照
Apr 07 Javascript
JS实现的全排列组合算法示例
Oct 09 Javascript
JavaScript实现短信倒计时60s
Oct 09 Javascript
npm 更改默认全局路径以及国内镜像的方法
May 16 Javascript
javascript实现文本框标签验证的实例代码
Oct 14 Javascript
详解JavaScript中jQuery和Ajax以及JSONP的联合使用
Aug 13 #Javascript
JavaScript的面向对象编程基础
Aug 13 #Javascript
JavaScript简单判断复选框是否选中及取出值的方法
Aug 13 #Javascript
JavaScript实现将文本框的值插入指定位置的方法
Aug 13 #Javascript
JavaScript的jQuery库中function的存在和参数问题
Aug 13 #Javascript
js实现仿Discuz文本框弹出层效果
Aug 13 #Javascript
深入学习JavaScript中的原型prototype
Aug 13 #Javascript
You might like
PHP实现多条件查询实例代码
2010/07/17 PHP
深入解析PHP内存管理之谁动了我的内存
2013/06/20 PHP
CI(CodeIgniter)框架介绍
2014/06/09 PHP
PHP return语句另类用法不止是在函数中
2014/09/17 PHP
PHP实现通过中文字符比率来判断垃圾评论的方法
2014/10/20 PHP
php实现微信支付之现金红包
2018/05/30 PHP
PHP 实现base64编码文件上传出现问题详解
2020/09/01 PHP
javascript setTimeout()传递函数参数(包括传递对象参数)
2010/04/07 Javascript
jQuery Study Notes学习笔记 (二)
2010/08/04 Javascript
jquery星级插件、支持页面中多次使用
2012/03/25 Javascript
js导航栏单击事件背景变换示例代码
2014/01/13 Javascript
JavaScript中判断变量是数组、函数或是对象类型的方法
2015/02/25 Javascript
Javascript中indexOf()和lastIndexOf应用方法实例
2016/08/24 Javascript
JS返回只包含数字类型的数组实例分析
2016/12/16 Javascript
详解探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 构建记事本应用
2017/06/16 Javascript
微信小程序非swiper组件实现的自定义伪3D轮播图效果示例
2018/12/11 Javascript
vue.js iview打包上线后字体图标不显示解决办法
2020/01/20 Javascript
vue 将多个过滤器封装到一个文件中的代码详解
2020/09/05 Javascript
Python通过正则表达式选取callback的方法
2015/07/18 Python
Windows下Anaconda的安装和简单使用方法
2018/01/04 Python
Python数据报表之Excel操作模块用法分析
2019/03/11 Python
Django如何实现网站注册用户邮箱验证功能
2019/08/14 Python
vue常用指令代码实例总结
2020/03/16 Python
Python尾递归优化实现代码及原理详解
2020/10/09 Python
你正在寻找的CSS3 动画技术
2011/07/27 HTML / CSS
详解CSS3中常用的样式【基本文本和字体样式】
2020/10/20 HTML / CSS
HTML5+CSS3模仿优酷视频截图功能示例
2017/01/05 HTML / CSS
使用HTML5做的导航条详细步骤
2020/10/19 HTML / CSS
JACK & JONES英国官方网站:欧洲领先的男装生产商
2017/09/27 全球购物
享受加州生活方式的时尚舒适:XCVI
2018/07/09 全球购物
Linden Leaves官网:新西兰纯净护肤品
2020/12/20 全球购物
介绍下static、final、abstract区别
2015/01/30 面试题
2014年医院后勤工作总结
2014/12/06 职场文书
介绍信格式
2015/01/30 职场文书
win10+anaconda安装yolov5的方法及问题解决方案
2021/04/29 Python
解决MultipartFile.transferTo(dest) 报FileNotFoundExcep的问题
2021/07/01 Java/Android