理解javascript函数式编程中的闭包(closure)


Posted in Javascript onMarch 08, 2016

闭包(closure)是函数式编程中的概念,出现于 20 世纪 60 年代,最早实现闭包的语言是 Scheme,它是 LISP 的一种方言。之后闭包特性被其他语言广泛吸纳。
闭包的严格定义是“由函数(环境)及其封闭的自由变量组成的集合体。”这个定义对于大家来说有些晦涩难懂,所以让我们先通过例子和不那么严格的解释来说明什么是闭包,然后再举例说明一些闭包的经典用途。

什么是闭包

通俗地讲, JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体
现出闭包的特性,请看下面这个例子:

var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter = generateClosure();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2
console.log(counter()); // 输出 3

这段代码中, generateClosure() 函数中有一个局部变量count, 初值为 0。还有一个叫做 get 的函数, get 将其父作用域,也就是 generateClosure() 函数中的 count 变量增加 1,并返回 count 的值。 generateClosure() 的返回值是 get 函数。在外部我们通过 counter 变量调用了 generateClosure() 函数并获取了它的返回值,也就是 get 函数,接下来反复调用几次 counter(),我们发现每次返回的值都递增了 1。
让我们看看上面的例子有什么特点,按照通常命令式编程思维的理解, count 是generateClosure 函数内部的变量,它的生命周期就是 generateClosure 被调用的时期,当 generateClosure 从调用栈中返回时, count 变量申请的空间也就被释放。问题是,在 generateClosure() 调用结束后, counter() 却引用了“已经释放了的” count变量,而且非但没有出错,反而每次调用 counter() 时还修改并返回了 count。这是怎么回事呢?
这正是所谓闭包的特性。当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭 包 不 但 包 括 被 返 回 的 函 数 , 还包括这个函数的定义环境。上面例子中,当函数generateClosure() 的内部函数 get 被一个外部变量 counter 引用时, counter 和generateClosure() 的局部变量就是一个闭包。如果还不够清晰,下面这个例子可以帮助
你理解:

var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter1 = generateClosure();
var counter2 = generateClosure();
console.log(counter1()); // 输出 1
console.log(counter2()); // 输出 1
console.log(counter1()); // 输出 2
console.log(counter1()); // 输出 3
console.log(counter2()); // 输出 2

上面这个例子解释了闭包是如何产生的:counter1 和 counter2 分别调用了 generateClosure() 函数,生成了两个闭包的实例,它们内部引用的 count 变量分别属于各自的运行环境。我们可以理解为,在generateClosure() 返回 get 函数时,私下将 get 可能引用到的 generateClosure() 函数的内部变量(也就是 count 变量)也返回了,并在内存中生成了一个副本,之后 generateClosure() 返回的函数的两个实例 counter1和 counter2 就是相互独立的了。

闭包的用途

1、嵌套的回调函数
闭包有两个主要用途,一是实现嵌套的回调函数,二是隐藏对象的细节。让我们先看下面这段代码示例,了解嵌套的回调函数。如下代码是在 Node.js 中使用 MongoDB 实现一个简单的增加用户的功能:

exports.add_user = function(user_info, callback) {
var uid = parseInt(user_info['uid']);
mongodb.open(function(err, db) {
if (err) {callback(err); return;}
db.collection('users', function(err, collection) {
if (err) {callback(err); return;}
collection.ensureIndex("uid", function(err) {
if (err) {callback(err); return;}
collection.ensureIndex("username", function(err) {
if (err) {callback(err); return;}
collection.findOne({uid: uid}, function(err) {
if (err) {callback(err); return;}
if (doc) {
callback('occupied');
} else {
var user = {
uid: uid,
user: user_info,
};
collection.insert(user, function(err) {
callback(err);
});
}
});
});
});
});
});
};

如果你对 Node.js 或 MongoDB 不熟悉,没关系,不需要去理解细节,只要看清楚大概的逻辑即可。这段代码中用到了闭包的层层嵌套,每一层的嵌套都是一个回调函数。回调函数不会立即执行,而是等待相应请求处理完后由请求的函数回调。我们可以看到,在嵌套的每一层中都有对 callback 的引用,而且最里层还用到了外层定义的 uid 变量。由于闭包机制的存在,即使外层函数已经执行完毕,其作用域内申请的变量也不会释放,因为里层的函数还有可能引用到这些变量,这样就完美地实现了嵌套的异步回调。

2、实现私有成员
我们知道, JavaScript 的对象没有私有属性,也就是说对象的每一个属性都是曝露给外部的。这样可能会有安全隐患,譬如对象的使用者直接修改了某个属性,导致对象内部数据的一致性受到破坏等。 JavaScript通过约定在所有私有属性前加上下划线(例如_myPrivateProp),表示这个属性是私有的,外部对象不应该直接读写它。但这只是个非正式的约定,假设对象的使用者不这么做,有没有更严格的机制呢?答案是有的,通过闭包可以实现。让我们再看看前面那个例子:

var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter = generateClosure();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2
console.log(counter()); // 输出 3

我们可以看到,只有调用 counter() 才能访问到闭包内的 count 变量,并按照规则对其增加1,除此之外决无可能用其他方式找到 count 变量。受到这个简单例子的启发,我们可以把一个对象用闭包封装起来,只返回一个“访问器”的对象,即可实现对细节隐藏。

以上就是本文的全部内容,希望能够帮助大家更好的学习理解javascript闭包。

Javascript 相关文章推荐
拖动布局之保存布局页面cookies篇
Oct 29 Javascript
基于jquery的jqDnR拖拽溢出的修改
Feb 12 Javascript
基于jquery实现的鼠标拖拽元素复制并写入效果
Aug 23 Javascript
asp.net 30分钟掌握无刷新 Repeater
Sep 16 Javascript
D3.js实现散点图和气泡图的方法详解
Sep 21 Javascript
JS取数字小数点后两位或n位的简单方法
Oct 24 Javascript
Vue.js简易安装和快速入门(第二课)
Oct 17 Javascript
基于Vue 2.0的模块化前端 UI 组件库小结
Dec 21 Javascript
node 使用 async 控制并发的方法
May 07 Javascript
vue-cli 脚手架基于Nightwatch的端到端测试环境的过程
Sep 30 Javascript
JQuery省市联动效果实现过程详解
May 08 jQuery
jQuery--遍历操作实例小结【后代、同胞及过滤】
May 22 jQuery
jQuery实现带水平滑杆的焦点图动画插件
Mar 08 #Javascript
javascript对象的创建和访问
Mar 08 #Javascript
js获取当前日期时间及其它日期操作汇总
Mar 08 #Javascript
使用JQuery实现智能表单验证功能
Mar 08 #Javascript
js表单处理中单选、多选、选择框值的获取及表单的序列化
Mar 08 #Javascript
JS实现设置ff与ie元素绝对位置的方法
Mar 08 #Javascript
Angularjs material 实现搜索框功能
Mar 08 #Javascript
You might like
《星际争霸》各版本雷兽特点图文解析 雷兽不同形态一览
2020/03/02 星际争霸
正义联盟的终局之战《天启星战争》将成为DC动画宇宙的最后一部
2020/04/09 欧美动漫
提升PHP速度全攻略
2006/10/09 PHP
elgg 获取文件图标地址的方法
2010/03/20 PHP
PHP实现设计模式中的抽象工厂模式详解
2014/10/11 PHP
php实现数组按指定KEY排序的方法
2015/03/30 PHP
PHP实现QQ登录实例代码
2016/01/14 PHP
JQuery在光标位置插入内容的实现代码
2010/06/18 Javascript
jquery 使用点滴函数代码
2011/05/20 Javascript
js导航栏单击事件背景变换示例代码
2014/01/13 Javascript
jquery结合CSS使用validate实现漂亮的验证
2015/01/29 Javascript
深入分析Cookie的安全性问题
2015/03/01 Javascript
JavaScript数组对象实现增加一个返回随机元素的方法
2015/07/27 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
2016/07/07 Javascript
JavaScript中访问id对象 属性的方式访问属性(实例代码)
2016/10/28 Javascript
微信小程序调用PHP后台接口 解析纯html文本
2017/06/13 Javascript
Vue 滚动行为的具体使用方法
2017/09/13 Javascript
JS获取数组中出现次数最多及第二多元素的方法
2017/10/27 Javascript
jquery获取transform里的值实现方法
2017/12/12 jQuery
浅谈vue中关于checkbox数据绑定v-model指令的个人理解
2018/11/14 Javascript
js实现网页随机验证码
2020/10/19 Javascript
[50:48]LGD vs CHAOS 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
在Python的Django框架下使用django-tagging的教程
2015/05/30 Python
利用python爬取散文网的文章实例教程
2017/06/18 Python
Python使用numpy产生正态分布随机数的向量或矩阵操作示例
2018/08/22 Python
Python @property装饰器原理解析
2020/01/22 Python
pycharm 2018 激活码及破解补丁激活方式
2020/09/21 Python
总结html5自定义属性有哪些
2020/04/01 HTML / CSS
MONNIER Frères英国官网:源自巴黎女士奢侈品配饰电商平台
2018/12/06 全球购物
幼儿教师研修感言
2014/02/12 职场文书
学习焦裕禄同志为人民服务思想汇报
2014/09/10 职场文书
学校重阳节活动总结
2015/03/24 职场文书
js中Object.create实例用法详解
2021/10/05 Javascript
MySQL分区表实现按月份归类
2021/11/01 MySQL
拙作再改《我的收音机情缘》
2022/04/05 无线电
win10蓝屏0xc0000001安全模式进不了怎么办?win10出现0xc0000001的解决方法
2022/08/05 数码科技