我对javascript很新,最近在了解闭包时我遇到了一个问题,面试官问了一个问题: -
function initButtons() {
var body = document.body,
button, i;
for (i = 0; i < 5; i++) {
button = document.createElement("button");
button.innerHTML = "Button " + i;
button.addEventListener("click", function (e) {
alert(i);
}, false);
body.appendChild(button);
}
}
initButtons();
此代码的输出结果是什么?我回答了 - “对应于按钮的数字...... 1,2等”
好的,然后我用Google搜索并找到答案,其中说: -
发生这种情况的原因是因为addEventListener方法是 在for循环的每次迭代期间调用,都会创建一个闭包。
好的,现在一切都在我头顶......这怎么可能?坦率地说,我是一个愚蠢的JavaScript,并试图尽可能多地学习。所以,我要通过基础知识!!
的详细信息答案 0 :(得分:14)
除此部分外,其中大部分都会按预期方式工作:
button.addEventListener("click", function (e) {
alert(i);
}, false);
有人可能会认为每个addEventListener
都有三个参数:
"click"
false
如果是这种情况,五个事件监听器将如下所示:
button.addEventListener("click", alert(0), false);
button.addEventListener("click", alert(1), false);
button.addEventListener("click", alert(2), false);
button.addEventListener("click", alert(3), false);
button.addEventListener("click", alert(4), false);
但是,设置为第二个参数的函数的结果不是本身。所以实际上,这是你的五个事件听众:
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
如您所见,所有这些参数的第二个参数是以下函数:
function(e){
alert(i);
}
因此,当您单击其中一个按钮时,将执行上述功能。所以你点击一个按钮然后执行该功能,这是一个有效的问题:i
的价值是多少?背景在这里很重要。上下文不是循环的中间,上下文是你点击了按钮并触发了这个函数。
聪明的猜测是i
在此上下文中未定义。但是当在另一个函数内创建函数时会发生这种棘手的事情:子函数可以访问父函数中的变量。所以i
仍然可用!那么现在i
是什么?它是5
,因为我们已将它循环到5,它仍为5.
所以当你听到人们谈论闭包时,他们真正的意思是“子功能”。子功能很棘手,因为它们以这种方式携带父母的变量。
那么如果你想按下按钮alert
点击按钮对应的数字呢?如果使用立即执行的函数,则可以执行此操作,因此返回函数的结果,而不是函数本身。为此,您只需将函数表达式包装在parens中,然后使用();
。
var one = function(){} // one contains a function.
var two = (function(){})(); // two contains the result of a function
让我们稍微玩一下,看看它会如何影响代码。
我们可以使事件监听器的第二个参数立即执行like so:
button.addEventListener("click", (function (e) {
alert(i);
})(), false);
然后你会在加载页面后立即得到5个警报,0-4,点击时没有任何警告,因为该函数没有返回任何内容。
这不是我们真正想要的。我们真正想要的是让addEventListener
从该循环中触发。简单易用 - 只需将其包装在一个立即执行的功能中:
(function(i){
button.addEventListener("click", function (e) {
alert(i);
}, false);
})(i);
请注意,我们现在();
代替(i);
而不是i
,因为我们将参数button.addEventListener("click", function (e) {
alert(0);
}, false);
button.addEventListener("click", function (e) {
alert(1);
}, false);
button.addEventListener("click", function (e) {
alert(2);
}, false);
button.addEventListener("click", function (e) {
alert(3);
}, false);
button.addEventListener("click", function (e) {
alert(4);
}, false);
传递给它,但除此之外它们都是相同的。现在该函数立即被触发,a.k.a。我们将立即获得该函数的结果。这个新的立即执行功能的结果是什么?它实际上会根据循环的迭代次数而改变。以下是循环每次迭代的函数结果:
i
因此,您现在可以看到,如果function newFunc(){}
在其中进行了硬编码,那么将在点击时触发的五个函数都具有值。如果你得到这个,你会得到关闭。就个人而言,我不明白为什么人们在解释问题时会使问题过于复杂。请记住以下内容:
( ... )();
)使用作为函数而不是作为函数的结果即可。 就我个人而言,我发现这是一种更直观的方式来理解闭包而没有所有疯狂的术语。
答案 1 :(得分:3)
编写代码,如
function initButtons() {
var body = document.body, button, i;
for (i = 0; i < 5; i++) {
(function(i) {
button = document.createElement("button");
button.innerHTML = "Button " + i;
button.addEventListener("click", function (e) {
alert(i);
}, false);
body.appendChild(button);
}(i));
}
}
initButtons();
否则,所有警报都将显示i
的最后结果,因为在您的代码中i
位于全局范围内,并且每次循环发生时它都会更新。在这个例子中,wrapper / anonymous here函数只是将范围分开。
答案 2 :(得分:0)
上面给出的解释很好。但是,现在有一种更简单的方法,而不是使用( I 立即 I 调用 F 连接 E xpression)< strong> IIFE 。
ECMAScript 2015年引入了一个新关键字let
。 let
与var
的工作方式类似,不同之处在于它是以块为单位因此,只需引入let
关键字即可重构代码。
function initButtons() {
var body = document.body,
button;
for (let i = 0; i < 5; i++) {
button = document.createElement("button");
button.innerHTML = "Button " + i;
button.addEventListener("click", function (e) {
alert(i);
}, false);
body.appendChild(button);
}
}
initButtons();