0

我从未使用过addEventListener(),但由于我生成内容的方式,我无法为每个<div>我将其视为按钮的 HTML 等效项编写。相当于:

<div onmousedown="jsItems[someId].toggleImage(someGallery, someIndex);"></div>

我一直在尝试的是:

JsTree.prototype.addGalleries = function(inElements) {
    // ...unrelated code here removed for StackOverflow...

    for (var i = 0; i < this.jsGalleries.length; i++) {
        for (var j = 0; j < this.jsGalleries[i].buttons.length; j++) {
            var self = this;
            this.jsGalleries[i].buttons[j].addEventListener("mousedown", function() {
                self.toggleImage(i, j);
            });
        }
    }
}

其中i从 0 到 1j计数和从 0 到 2 计数(i在这种情况下都是),i表示someGalleryj表示someIndex,我可以在上面的代码内部(或使用someIdinside的函数)访问。this.idself.idaddEventListener

问题是,尽管单击这些“按钮”(<div>s )之一确实会触发:

JsTree.prototype.toggleImage = function(inGallery, inIndex) {
    alert(this.id+", "+inGallery+", "+inIndex);
}

无论单击哪个按钮,它都会始终提示“8、2、3”。“8”是正确的,但我不知道为什么会提醒“2”或“3”。它们似乎比什么多 1i并且j计数(通过尝试j < this.jsGalleries[i].buttons.length-1哪个警报“8、2、2”来验证)。

编辑: someId,someGallerysomeIndex不是真正的变量,它们是我为了解释问题而编造的垃圾。

4

2 回答 2

3

这是一个典型的 JS 错误。问题是 和 的值i没有j在任何函数范围内捕获,并且您的事件处理程序是异步的。这意味着当您的事件处理程序运行时,两个for循环都已运行完成,因此i == this.jsGalleries.lengthj === this.jsGalleries[this.jsGalleries.length - 1].buttons.length.

尝试其中之一:

JsTree.prototype.addGalleries = function(inElements) {
  // ...unrelated code here removed for StackOverflow...

  for (var i = 0; i < this.jsGalleries.length; i++) {
    for (var j = 0; j < this.jsGalleries[i].buttons.length; j++) {
      (function(self, innerI, innerJ){
        var galleryEl = self.jsGalleries[innerI].buttons[innerJ];
        galleryEl.addEventListener("mousedown", function() {
          self.toggleImage(innerI, innerJ);
        });
      })(this, i, j);
    }
  }
}

或者可能更清楚:

JsTree.prototype.addGalleries = function(inElements) {
  // ...unrelated code here removed for StackOverflow...

  var addHandler = function(self, i, j){
    self.jsGalleries[i].buttons[j].addEventListener("mousedown", function() {
      self.toggleImage(i, j);
    });
  };

  for (var i = 0; i < this.jsGalleries.length; i++) {
    for (var j = 0; j < this.jsGalleries[i].buttons.length; j++) {
      addHandler(this, i, j);
    }
  }
}
于 2013-07-08T05:25:15.263 回答
1

addEventListener 没有问题。这是一个常见的错误。为了理解发生了什么,我必须解释闭包是如何工作的。

当您在其中有一个循环和一个函数时:

var i = 5;
while(i--){
  setTimeout(function(){
    console.log(i);
  }, 100);
}

每个函数都有一个对变量的引用i。这意味着它们不会保留i您定义它们时的值。再次重申,每个函数都引用同一个变量i,而不是声明函数时的值。在我上面的例子中,所有的 setTimeout 都是异步定义的。匿名函数都在 100 毫秒后触发,并且每个函数都会记录i函数运行时的值。在我的示例中,对于所有函数,该值都是 -1。

有两种方法可以解决这个问题。我先给你看一个简单的:

for (var i = 0; i < this.jsGalleries.length; i++) {
    for (var j = 0; j < this.jsGalleries[i].buttons.length; j++) {
        var self = this;
        self.gallery = {i: i, j: j};
        this.jsGalleries[i].buttons[j].addEventListener("mousedown", function() {
            self.toggleImage(self.gallery.i, self.gallery.j);
        });
    }
}

在这里,您将值存储在实际的 DOM 元素上。这些值等同于循环运行时的值,因此事件侦听器获取正确的值。请注意,我将值嵌套在一个名为 gallery 的对象中。我这样做是为了给它命名空间。将值存储在 DOM 中的元素上并不是一个好主意,以防浏览器最终实现具有相同名称的属性。我觉得画廊足够安全。

解决此问题的另一种选择(可能是最佳实践)是使用闭包来发挥您的优势。

for (var i = 0; i < this.jsGalleries.length; i++) {
    for (var j = 0; j < this.jsGalleries[i].buttons.length; j++) {
        var self = this;
        this.jsGalleries[i].buttons[j].addEventListener("mousedown", (function closure(self, i, j){
            return function actualListener(){
                self.toggleImage(i, j);
            }
        })(self, i, j));
    }
}

在这种情况下,我们创建了一个自执行函数(在我的示例中称为闭包),它在我们创建侦听器时立即运行。让我再说一遍,这个函数在添加监听器的那一刻运行,而不是在它运行的时候。我们这样做的原因是我们可以传入我们想要保存以供以后使用的值,在本例中为 self、i 和 j。然后,当事件发生时,实际运行的函数是内部函数(称为actualListener)。在闭包函数运行时,actualListener 拥有存储在其闭包中的所有值的副本。

于 2013-07-08T05:26:02.520 回答