看此篇文章前,请确保已有闭包的知识储备~
一、问题引入
页面上五个完全相同的按钮,现在想实现点哪个按钮,哪个按钮就弹出自己是第几个。
<button>click me!</button>
<button>click me!</button>
<button>click me!</button>
<button>click me!</button>
<button>click me!</button>
乍一看,这不是很简单嘛,一个循环+一个绑定事件就搞定:
var btns = document.getElementsByTagName("button");
for (var = 0; i < btns.length; i++) {
btns[i].onclick = function() {
console.log(i);
}
}
兴高采烈的点开按钮,你却发现每个按钮点出来都弹的是5
原来是因为:
一般单击按钮,都只能在循环结束后。
等到循环后再去单击按钮,调用的就是按钮上提前保存的事件处理函数
二、回调函数等待队列
这个时候我们就要了解一个概念,在程序执行中不仅有主程序,还有一个主程序之外的队列,回调函数等待队列
只有主程序中所有代码都执行完,才能从回调队列中取函数,进入主程序执行。
以上述问题作为例子,程序运行图如下:
主程序循环之后i=5,然后再去执行回调函数等待队列,故所有console.log输出结果都为5
三、解决方案
方法一:闭包函数
使用 匿名函数自调用,每次将传入的值i放在局部变量中,并使局部变量和onclick事件形成闭包,局部变量被永久保存起来。
此处闭包中函数不用返回到外部的原因:
按钮一直存在,属于全局定义,不用return
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
(function(i){ //循环几次,生成几个单独的闭包
// var i; //如果有形参,相当于局部变量
btns[i].onclick = function() {
alert(i);
}
})(i);
}
console.log(i)
方法二:let
let在底层自动转换为匿名函数自调用,其实等效于方法一
当let碰上for:匿名函数自调擅自加工加入了循环变量,此处为i
var btns = document.getElementsByTagName("button");
// let底层原理就是匿名函数自调,闭包
for (let i = 0; i < btns.length; i++) {
// (function(i){
btns[i].onclick = function() {
console.log(i);
}
// })(i);
}