闭包的问题.任何提示

Closures issue. Any tips?

本文关键字:提示 任何 问题 闭包      更新时间:2023-09-26

我有一个问题,下面的代码是与闭包相关的,我需要一些帮助。

正如你所看到的,我在for循环中创建了几个图像,我分配了不同的id(即。数组中的数字)。到目前为止,一切顺利。当我点击不同的图像时,我希望函数showId被调用,图像id作为参数,但问题是,用作函数参数的数字总是变成nr 8(数组中的最后一个数字)。我怎么解决这个问题?

提前感谢。

var imageArea = document.getElementById('imageArea');
var nrArray = [1,2,3,4,5,6,7,8];
for (var i = 0; i < nrArray.length; i++){
    var image = document.createElement('img');
    image.src = "images/theimage.png";
    image.id = nrArray[i];
    imgArea.appendChild(image);
    image.addEventListener ('click',function() {showId(image.id);},false);
}

对于这个完全相同的问题,stackoverflow上有无数的答案,我将在一分钟内找到一些,但这里是关键点:

  • Javascript有词法作用域(不是动态作用域)
  • Javascript中最小的Javascript作用域是function
  • Block 没有有自己的作用域
  • Javascript使用变量提升,这意味着在作用域执行之前找到作用域中的所有变量,并将其移动到作用域的开头。也就是说var image = ...不在你想的地方。只有一个image变量,它(由于词法作用域)是你在闭包中访问的变量,它在循环结束时显然会指向最后一个迭代项。
  • 函数是一级对象,这意味着它们被当作变量对待,可以像这样分配和传递
  • 你可以通过创建一个自动执行的匿名函数来为变量创建一个稳定的作用域

例如:

(function(localImage) {
    image.addEventListener ('click',function() {showId(localImage.id);},false);
})(image);

另外,正如其他人指出的那样,事件侦听器闭包是在上下文中执行的。因此,反过来,不必担心使用close来修复作用域,您可以这样做:

image.addEventListener ('click',function() {showId(this.id);},false);

编辑

一些类似问题的链接和对答案的不同观点:

  • 在子函数中访问循环迭代?
  • for循环和词法环境中的闭包
  • JavaScript中奇怪的东西" for "
  • Javascript循环中的事件处理程序-需要闭包吗?

通过使用this关键字引用父对象来访问ID:

//In this case, this refers to the object that owns the function, i.e., your img
image.addEventListener ('click',function() {showId(this.id);},false);

为什么不用this呢?

var imgArea = document.getElementById('imageArea');
var nrArray = [1,2,3,4,5,6,7,8];
for (var i = 0; i < nrArray.length; i++) {
    var image = document.createElement('img');
    image.src = "images/theimage.png";
    image.id = nrArray[i];
    imgArea.appendChild(image);
    image.addEventListener('click', function() {
        showId(this.id);
    }, false);
}

另外,你最初的问题是关于创建闭包,所以即使我确信使用this将提供您现在需要的,我还是要添加一个创建新作用域的小演示,使您能够利用闭包来完成相同的任务:

var imgArea = document.getElementById("imageArea");
var nrArray = [1, 2, 3, 4, 5, 6, 7, 8];
for (var i = 0; i < nrArray.length; i++) {
    createClosure(i);
}
function createClosure(i) {
    var image = document.createElement('img');
    image.src = "images/theimage.png";
    image.id = nrArray[i];
    imgArea.appendChild(image);
    image.addEventListener('click', function () {
        showId(image.id);
    }, false);
}

jsFiddle demo: http://jsfiddle.net/HMYsW/1/

James Hill的回答指出了解决这种情况的简单方法,但当然,您仍然存在闭包问题。一旦您想要在事件侦听器中使用的数据作为环境的一部分不可用(例如,作为this的属性),您将再次遇到同样的问题。

要解决这个问题,您需要创建一个新的闭包(即function):

function createListener(id) {
  return function() { showId(id); }
}
var imageArea = document.getElementById('imageArea');
for (var i = 1; i <= 8; i++){
    var image = document.createElement('img');
    image.src = "images/theimage.png";
    image.id = i;
    imgArea.appendChild(image);
    image.addEventListener ('click', createListener(image.id), false);
}

这里有一个创建并返回另一个函数的函数。返回的函数将是实际的事件监听器。在创建过程中,变量id被关闭并保持其值。当然你可以一步完成:

image.addEventListener ('click', function (id) {
 return function() { showId(id); };
}(image.id), false);

一旦代码超出作用域,Javascript不会自动从内存中删除变量。这意味着即使你在一个特定的范围内声明变量,在你的例子中是for循环,在这个范围之外,变量仍然存在。

附加到所有图像的事件侦听器正在执行$showId(image.id);行然而,由于变量没有从内存中删除,变量image仍然存在,即for循环的最后一个。这就是为什么你总是从数组中得到最后一个数字:8。

当eventlistener被调用时,它将变量this设置为自己的元素,因此将变量从image更改为this将解决问题。addEventListener调用变成:

image.addEventListener ('click',function() {showId(image.id);},false);