前言:
关于闭包的入门篇在这里,初步引入了闭包的概念,在这篇文章中有一个计数器的案例,如果要兼顾计数变量的安全性和计数功能的实现,使用闭包是一个很好的办法
关于计数器的实现方法,这篇文章的最后提供了另一种思路:使用函数的对象特性,巴拉巴拉
正文
闭包三要素
1.嵌套结构的函数
2.内部函数访问了外部函数的变量
3.在外部函数之外,调用内部函数
作用
1.让局部变量变成私有化的长存变量
2.给事件调用的函数传递参数
<script>
function f(){
var x = 1;
function e(){
x++;
console.log(x);
}
return e;//返回内部函数的指针,让外部函数之外也能获取到e函数
}
var e = f();
e();//调用一次,计数一次,x自增一次
</script>
复制代码
或者内部函数赋值给全局变量,扩大作用域,让内部函数可以在大环境下被调用,运行结果同上
<script>
var e;
function f(){
var x = 1;
e = function (){
x++;
console.log(x);
}// 全局
}
f();
e();//调用一次,计数一次
</script>
复制代码
也可以有多个内部函数,操作外部函数的同一个变量
<script>
var e1;
var e2;
function f(){
var x = 1;
e1 = function (){
x++;
console.log(x);
}
e2 = function(){
x--;
console.log(x);
}
}
f();
e1();//调用一次,计数一次
</script>
复制代码
运行结果:
闭包究竟是什么?
概念:闭包是由函数以及创建该函数的语法环境组成的,这个环境包含了这个闭包创建时所能访问的所有局部变量
就像是有一个简易的类特性:
闭包操作可以看成闭包工厂和闭包对象构成;
由闭包工厂生成闭包对象,每个闭包对象互不干扰;
闭包对象中都存储着工厂给的局部变量,和一个函数,有一个指针指向闭包对象中的函数,使用这个函数;
闭包对象在内存中保存着,直到很长一段时间内都没有使用,系统自动回收;(所以那个x,没有在f函数执行结束之后就失效)
//闭包工厂(函数)
function f(){
var x = 1;
function e(){
x++;
console.log(x);
}
return e;
}
var f1 = f();//闭包对象1
var f2 = f();//闭包对象2
复制代码
运行结果:执行了3次f1后,f1中x的值已经到了4,此时执行一次f2,打印出x的值为2,可以看出,两个闭包对象不干扰
常见错误:
场景:循环内使用同一个闭包对象,造成变量累加
<ul>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>
<script>
function showId(id){
console.log(id);
}
function setClick(){
var ary = document.getElementsByTagName('li');
for (var i = 0; i < ary.length; i++){
ary[i].onclick = function(){
showId(i+1);
}
}
}
setClick();
</script>
复制代码
运行结果:不论点击哪一个li,都是输出4
解析:
setClick函数满足了闭包的要素,里面的匿名函数使用了外部函数创建的变量i,上面代码的for循环,执行之后的结果是:
可以看到,setClick函数执行后,i已经变成3,并且长存在内存中,所以当我们点击其中一个li,showId出来的,都是4
我们可以通过两种办法解决这个问题
方法一:使用闭包工厂创建多个闭包对象
function showId(id){
console.log(id);
}
function clickFunc(id){
function e(){
showId(id);
}
return e;
}
function setClick(){
var ary = document.getElementsByTagName('li');
for (var i = 0; i < ary.length; i++){
ary[i].onclick = clickFunc(i + 1);
}
}
setClick();
复制代码
运行效果:
方法二:使用立即执行函数,创建多个闭包对象
function showId(id){
console.log(id);
}
function setClick(){
var ary = document.getElementsByTagName('li');
for (var i = 0; i < ary.length; i++){
(function (){
var id = i;
ary[id].onclick = function(){
showId(id + 1);
}
})();
}
}
复制代码
效果同上:
调试如下:
或者不使用闭包
function setClick(){
var ary = document.getElementsByTagName('li');
for (var i = 0; i < ary.length; i++){
let id = i;
ary[id].onclick = function(){
showId(id + 1);
}
}
}
复制代码
效果同上
调试出的各个li的onclick函数也一样
整理一下,应该是,
因为setClick函数里,循环时定义的变量i,在内部函数被直接使用,所以setClick相当于是一个闭包工厂,它的局部变量i是长存的状态,ary[i].onclick在同一个闭包对象中;
当我们点击其中一个li时,showId得到的参数是i+1=3+1=4,而i到达3之后,在闭包中是不再存储累加结果的,所以showId得到的i一直是3;
如果我们将i与id区分开,就能避免i的累加对内部函数参数的影响,即在setClick的内部函数的内部创建了多个闭包对象(记为ID1,ID2,ID3),i依然长存,但不影响闭包对象ID们的参数
循环时,随着i的累加,而创建出闭包对象,i+1已经传到变量id上了
所以,在使用for循环给内部函数传参时,如果不想要i的累加影响内部函数,简单的办法就是,使用立即执行,或直接用let定义一个变量,隔开内部函数与外部变量的直接联系
如果有更能说服我的理解,我再来更新