闭包就是一个函数引用另一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会增加内存消耗。或者说闭包就是子函数可以使用父函数的局部变量,还有父函数的参数。
最近在学ES6,学了let,突然想起一个我以前遇到的bug,相信也有很多同学也遇到过同样的bug。我们先看看下面的代码:
//html、js代码 <body> <button>a</button> <button>b</button> <button>c</button> </body> <script type="text/javascript"> var btn = document.getElementsByTagName('button'); for(var i = 0 ; i<btn.length;i++) { btn[i].onclick = function () { alert(i); } } </script>
我们运行这段代码,在按a这个按钮,会弹出什么?也许没碰到这个bug的会认为 弹出一个 ‘0’ ,但是 弹出的是 3 。
为什么弹出的是3?
在这里就要引入js闭包的概念。闭包,闭包就是能够读取其他函数内部变量的函数,在js中,函数(function)每次创建&emsp的时候都会生成一个闭包,这个闭包包含这个函数能访问的所有变量,闭包的一个意义在于可以让内部函数访问外部函数变量的作用域。
在这段代码中三个btn[i].onclick就创建了三个闭包,但是这三个闭包引用了同一个变量的作用域,那就是 i ,一个 i 被三个引用那肯定是会出问题的。
讲到这里,对于学c++或java的同学肯定会觉得不可理喻的,不就一个简单的for循环吗?怎么会出问题呢?当事实上它就是出问题了。
在se6还未诞生前,js没有块级作用域的说法,而在java、c++块级作用域是很常见的。
问题在于var,var定义的变量是有提升作用的,会自动提升成函数级作用域,而上述js代码中的 i 就被提升为全局变量了,所以不难理解为什么会三个函数同时引用同一个 i 了。
如何解决?
正常我们应该用 (function (i){ })(i); 即先创建一个匿名函数再调用这个函数。代码如下:
<body> <button>a</button> <button>b</button> <button>c</button> </body> <script type="text/javascript"> var btn1 = document.getElementsByTagName('button'); for (var i = 0; i < btn1.length; i++) { (function (t){ btn1[t].onclick = function () { alert(t); } })(i) } </script>
ES6中的let
let定义的变量:是块级作用域,不能重复定义。let的引入,可以让我们无形中解决一些bug。个人觉得还是有必要要用的。
有了let之后我们把var i ; 改为 let i;就没问题了,完美的解决了var这个函数级作用域的问题。
再讲讲闭包
闭包个人觉得有两大用处。
第一是为了让内部函数能调用外部函数的变量,从而使内部函数更加有意义。
第二是为了私有化变量,在java中有私有成员变量、私有局部变量,私有的存在让一个类有成为完美封装的存在,这个是java三大特点之一 封装。但是,js中没有 private 访问权限符,那怎么办?答案是闭包。
var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })();
上述代码就将privateCouter进行了闭包、封装,让privateCouter无法直接被访问、修改。
补充: 块级作用域,在每次遇到块(即中括号)时,会将 i 的引用取消,重新申请一个 i 的引用(但这个 i 不会被销毁,因为js的垃圾内存回收机制,i还存在闭包中的click的引用,emmmm有点低性能的感觉)。