关于javascript的闭包
闭包的定义:
闭包是函数式编程中的概念(lisp属于典型的函数式编程语言),其严格定义为:函数(环境)和其封闭的自由变量组成的集合体。通常我们在一个函数内定义了一个新函数,这个新函数内部引用了父函数中定义的变量,然后新函数被返回,这样就形成了一个闭包(当一个函数返回它内部定义的一个函数时,就形成了闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境)。示例代码如下:
1 function outer() { 2 var count = 1; 3 var get = function() { 4 count ++; 5 return count; 6 } 7 return get; 8 } 9 10 var new = outer(); 11 new(); //2 12 new(); //3 13 new(); //4 14 ...
按照命令式语言思维理解,当outer函数运行完之后,其内部申请存储count变量的空间将会被释放,返回的函数将不可能引用到count变量。然而,由于函数式编程中的闭包特性,当outer函数在调用栈中退出时,由于返回了outer函数中内部定义的函数实例,而这个被返回的函数实例引用了count变量,那么count变量就会在内存中存有一份副本。
注:函数返回内部定义的函数,返回的是一个实例。具体示例如下:
1 var generate = function() { 2 var count = 0; 3 function get() { 4 count ++; 5 return count; 6 } 7 return get; 8 } 9 10 var counter = generate(); 11 var counter1 = generate(); 12 13 counter() //1 14 counter1() //1 15 counter() //2 16 counter() //3 17 counter1() //2
详细解释上面的示例代码:两次调用generate函数将会分别返回独立的get函数数实例,并且get函数实例函数的运行环境(作用域)也分别被返回了(分别在内存中存有一份副本)。这样分别counter和counter1就是两个独立的函数,并且它们的运行环境也是完全独立的。counter和counter1的调用互不干扰。
闭包的用途:
1.嵌套的回调函数:
如下的ajax回调函数:
1 exports.add_user = function(user_info, callback) { 2 var uid = parseInt(user_info['uid']); 3 mongodb.open(function(err, db) { 4 if (err) {callback(err); return;} 5 db.collection('users', function(err, collection) { 6 if (err) {callback(err); return;} 7 collection.ensureIndex("uid", function(err) { 8 if (err) {callback(err); return;} 9 collection.ensureIndex("username", function(err) { 10 if (err) {callback(err); return;} 11 collection.findOne({uid: uid}, function(err) { 12 if (err) {callback(err); return;} 13 if (doc) { 14 callback('occupied'); 15 } else { 16 var user = { 17 uid: uid, 18 user: user_info, 19 }; 20 collection.insert(user, function(err) { 21 callback(err); 22 }); 23 } 24 }); 25 }); 26 }); 27 }); 28 }); 29 };
解释上面的代码:以上示例是用回调嵌套实现的将用户信息存入mongo的函数,其实质是一个闭包的嵌套。每个回调是在相应的请求完成后由请求函数进行回调,请求函数虽已执行完毕,但由于里层的函数有可能引用请求函数作用域中的变量,所以每个请求函数中变量都不会被释放。就像示例代码中所看到的,最里层的回调函数还引用了最外面的uid变量。
实现对象中的私有属性:
在javascript中对象没有私有属性,换言之对象的每一个属性都是暴露给外部的。这样会引发一系列的安全问题,例如兑现的使用者直接修改了对象的某个属性,导致对象内部数据一致性遭到破坏。Javascrip可以通过在属性前加上一个下划线(_privateProp)约定表示某个属性是私有属性,外部对象不应该直接使用它。但这只是个约定,你们懂的,世界上总有那么些不按约定办事的人,这样的约定并不能阻止外部对象直接访问某个私有属性。通过闭包,可以实现真正的私有属性。
回到前面的代码:
1 function generate() { 2 var count = 0; 3 var get = function() { 4 count ++; 5 return count; 6 } 7 return get; 8 } 9 10 11 var counter = generate(); 12 13 counter() //1 14 counter() //2 15 counter() //3 16 ...
在javascript中函数也是对象,因而函数中定义的变量,也算是函数对象的属性,虽然我们无法通过函数对象直接访问到这个属性,但是我们可以通过形成get的闭包函数,这样我们可以通过闭包函数去获取这个属性。