什么是闭包
通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性,请看下面这个例子:
var generateClosure = function(){ var count = 0; var get = function(){ count++; return count; } return get; } var counter1 = generateClosure(); var counter2 = generateClosure(); document.write(counter1());//1 document.write(counter2());//1 document.write(counter1());//2 document.write(counter1());//3 document.write(counter2());//2
按照通常命令式编程思维的理解,count 是generateClosure 函数内部的变量,它的生命周期就是generateClosure 被调用的时期,当 generateClosure 从调用栈中返回时,count 变量申请的空间也就被释放。问题是,在 generateClosure() 调用结束后,counter() 却引用了“已经释放了的” count 变量,而且非但没有出错,反而每次调用 counter() 时还修改并返回了count。这是怎么回事呢?
这正是所谓闭包的特性。当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。上面例子中,当函数generateClosure() 的内部函数get 被一个外部变量counter 引用时,counter 和generateClosure() 的局部变量就是一个闭包。
闭包的用途
1)嵌套的回调函数
exports.add_user = function(user_info, callback) { varuid = 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{ varuser = { uid: uid, user: user_info, }; collection.insert(user, function(err) { callback(err); }); } }); }); }); }); }); };
这段代码中用到了闭包的层层嵌套,每一层的嵌套都是一个回调函数。回调函数不会立即执行,而是等待相应请求处理完后由请求的函数回调。我们可以看到,在嵌 套的每一层中都有对 callback 的引用,而且最里层还用到了外层定义的 uid 变量。由于闭包机制的存在,即使外层函数已经执行完毕,其作用域内申请的变量也不会释放,因为里层的函数还有可能引用到这些变量,这样就完美地实现了嵌套 的异步回调。
2)实现私有成员
JavaScript 的对象没有私有属性,也就是说对象的每一个属性都是曝露给外部的。这样可能会有安全隐患,譬如对象的使用者直接修改了某个属性,导致对象内部数据的一致性 受到破坏等。JavaScript通过约定在所有私有属性前加上下划线(例如_myPrivateProp),表示这个属性是私有的,外部对象不应该直接 读写它。但这只是个非正式的约定,假设对象的使用者不这么做,有没有更严格的机制呢?答案是有的,通过闭包可以实现。
var generateClosure = function() { var count = 0; var get = function() { count ++; return count; }; return get; }; var counter = generateClosure(); document.write(counter()); // 输出1 document.write(counter()); // 输出2 document.write(counter()); // 输出3
只有调用counter() 才能访问到闭包内的 count 变量,并按照规则对其增加1,除此之外决无可能用其他方式找到count 变量。受到这个简单例子的启发,我们可以把一个对象用闭包封装起来,只返回一个“访问器”的对象,即可实现对细节隐藏。