1.什么是闭包?
W3C:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
要较好理解闭包,除了形式本身,还应先理解:
JS没有块级作用域:见JavaScript作用域;
JS的内存管理机制:
内存分配和回收的自动完成的,当定义变量,new对象,数组slice等操作时内存会自动分配,变量不再使用就会被回收。那么GC回收的关键在于如何判断变量不再使用,一般有如下两种方法。
- 引用计数,没有被引用的变量就被回收,缺点是循环引用易。老浏览器如IE6/7采用这种方式。新的采用标记清除。
- 标记清除,不可达的变量被回收。不可达指从根部扫描,始终不能访问到,孤立的一个或一些变量。这种方式判断的范围包括没被引用的变量和只有循环引用的孤立变量。
目前最流行的V8采取新生代老代的分代回收,与Java的GC思想类似。
2.闭包的例
闭包是一种结构,getName引用了外部变量name,形成闭包。第9行把name赋给变量whoname,使name在内存中保持。如果没有getName形成闭包,People函数执行完,其name变量就被释放。
1 function People(){ 2 var name = 'xiaohua'; 3 function getName(){ 4 return name; 5 } 6 return getName; 7 } 8 9 var whoName = new People();
3.闭包容易引起的bug
var result = []; function foo(){ var i = 0; for(;i<3;i++){ result[i] = function(){ alert(i); } } } foo(); result[0](); //3 result[1](); //3 result[2](); //3
这是因为i始终存在,没有块级作用域,i最后等于3,result数组中的所有函数里的i都指向那个等于3的i。
解决办法是给函数传参数:
1 result[i] = function(num){ 2 alert(num); 3 }
这种做法的原理涉及到作用域链。作用域链依次指向由近到远的作用域对象,上面给匿名函数添加一个参数,其最近作用域对象上就会有这个参数,参数是值类型,传递的只是副本,因此解决了上述问题。
4.闭包的几个用途
- 隔离作用域
JS虽然没有块级作用域,但是有函数作用域。为了使变量之间不会有命名冲突,使用立即执行函数把作用域隔离。下面的两个作用域内的name互不影响,无意间形成了闭包的结构。如果没有隔离作用域
1 (function(){ 2 var name = 'aa'; 3 4 function a(){ 5 return name; 6 } 7 })(); 8 9 (function(){ 10 var name = 'bb'; 11 12 function a(){ 13 return name; 14 } 15 })();
- 声明私有变量
JS本身没有私有变量、公共变量,静态变量的,都是模拟实现。下面如果把name作为People的属性,就无法实现私有。
1 var People = (function(){ 2 var name = "xiaohua"; 3 function People(){}; 4 People.prototype = { 5 getName:function(){ 6 return name; 7 }, 8 setName:function(newName){ 9 name=newName; 10 } 11 }; 12 return People; 13 })(); 14 15 var p = new People();
5.闭包的代价
闭包引用外部变量使得外部变量在其作用域执行完毕后不能立即销毁,必须等到闭包函数被销毁,引用清楚之后才能销毁。因此,闭包最明显的代价是占用内存。
其次,复杂闭包容易造成循环引用,在老版本浏览器导致内存泄漏。
参考: