JavaScript addEventListener()无法按预期工作

时间:2013-07-08 05:15:20

标签: javascript addeventlistener

我从未使用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到1计数,而j从0到2计数(在这种情况下对于i都是),i代表someGalleryj代表someIndex,我可以在上面的代码中使用someId访问this.id(或self.id的{​​{1}}函数内部addEventListener

问题是虽然点击其中一个“按钮”(<div> s)会触发:

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

无论点击哪个按钮,它始终会发出“8,2,3”警报。 “8”是正确的,但我不知道为什么“2”或“3”被警告。它们似乎只比ij计数多1(通过尝试j < this.jsGalleries[i].buttons.length-1验证“8,2,2”)。

修改someIdsomeGallerysomeIndex不是真正的变量,它们是我试图解释问题的垃圾。

2 个答案:

答案 0 :(得分:3)

这是一个经典的JS错误。问题是ij的值未在任何函数范围内捕获,并且您的事件处理程序是异步的。这意味着当您的事件处理程序运行时,两个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);
    }
  }
}

答案 1 :(得分: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。然后,当事件发生时,ACTUALLY运行的函数是内部函数(称为actualListener)。 actualListener在关闭函数运行时有一个存储在其闭包中的所有值的副本。