什么是闭包

  通俗地讲,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 变量。受到这个简单例子的启发,我们可以把一个对象用闭包封装起来,只返回一个“访问器”的对象,即可实现对细节隐藏。