本文解析JavaScript中常见的闭包陷阱,即在循环绑定事件时所有回调均引用最终变量值。通过引入立即执行的匿名函数创建独立作用域,将当前循环变量的值传递给内部函数,从而正确捕获每个迭代的变量状态,解决点击事件显示错误序号的问题。
<html> <head> <title></title> </head> <body> <div id="div" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">0</div> <div id="div1" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">1</div> <div id="div2" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">2</div> <div id="div3" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">3</div> <div id="div4" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">4</div> <div id="div5" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">5</div> <div id="div6" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">6</div> <input type="button" value="click" onclick="init();"> </body> </html>闭包导致的循环变量捕获问题
[Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
上述代码的初衷是为每个 div 元素绑定点击事件,期望在点击时显示对应的 div 序号。然而实际运行后发现,无论点击哪个 div,始终显示数字 7。造成这一现象的原因正是 JavaScript 中的闭包机制。
在 JavaScript 中,当函数内部定义另一个函数时,便形成了闭包。此时,外层函数的变量即使在外层函数执行结束后,仍可在内层函数中被访问。但当外层存在循环时,若内层函数直接引用该循环变量,它将始终引用该变量的最终值,而非每次迭代时的快照。
上述代码演示了这一现象。
利用匿名函数解决闭包问题
如何解决这一问题?
可以通过使用匿名函数来解决。利用匿名函数自动执行的特性,可以创建一个独立的作用域,并声明一个变量来引用外层的循环变量。
具体实现如代码所示:
<html> <head> <title></title> </head> <body> <div id="div" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">0</div> <div id="div1" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">1</div> <div id="div2" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">2</div> <div id="div3" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">3</div> <div id="div4" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">4</div> <div id="div5" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">5</div> <div id="div6" style=" border:1px solid #eee; background:#000;color:#fff;height:50px;width:300px">6</div> <input type="button" value="click" onclick="init();"> </body> </html>[Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
由于在内层函数中直接引用循环变量时获取的总是最终值,因此我们利用匿名函数激发出一个新的作用域。在进入内层循环之前,由另一个变量先获取该循环变量的当前值。这一思想是处理闭包问题的核心所在。
其他解决思路与总结
如下示例:虽然没有明显的 for 循环,但根据上述思想,可以立即解决该问题。
<html> <head> <title> </title> </head> <body> <div id="div" style="height:200px;width:300px"> </div> <input type="button" value="click" onclick="addDiv();"> </body> </html>[Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
解决后的代码如下:
<html> <head> <title> </title> </head> <body> <div id="div" style="height:200px;width:300px"> </div> <input type="button" value="click" onclick="addDiv();"> </body> </html>[Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
补充:有读者提出另一种解决方法:
<html> <head> <title> </title> </head> <body> <div id="div" style="background:#000;color:#fff;height:200px;width:300px"> </div> <input type="button" value="click" onclick="addDiv();"> </body> </html> [Ctrl+A 全选 注:引入外部Js需再刷新一下页面才能执行]
个人理解是,在进入内层循环之前,将 id 赋值给变量 f,并将 f 作为内层循环的参数传入。其核心思想与前述方法一致。