简体   繁体   English

JavaScript addEventListener()无法正常工作

[英]JavaScript addEventListener() not working as expected

I have never used addEventListener() , but I cannot write the HTML equivalent I would like for each <div> I am treating as a button because of the way I am generating content. 我从未使用过addEventListener() ,但是由于生成内容的方式,我无法为每个要作为按钮的<div>编写HTML等效代码。 The equivalent would be: 等效为:

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

What I've been trying is this: 我一直在尝试的是:

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);
            });
        }
    }
}

Where i counts from 0 to 1 and j counts from 0 to 2 (for both i in this case), i represents someGallery , j represents someIndex , and I could access someId with this.id inside the code above (or with self.id inside addEventListener 's function). 在这里i从0到1计数,而j从0到2计数(在这种情况下,对于i而言), i代表someGalleryj代表someIndex ,我可以在上面的代码中(或与self.id someId使用this.id访问self.idaddEventListener的函数中)。

The problem is that although clicking on one of these "buttons" ( <div> s) does trigger: 问题是,尽管单击这些“按钮”( <div> )之一确实会触发:

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

that it always alerts "8, 2, 3" regardless of which button is clicked. 无论单击哪个按钮,它始终会发出“ 8、2、3”警报。 The "8" is correct but I have no idea why "2" or "3" are alerted. “ 8”是正确的,但我不知道为什么会警告“ 2”或“ 3”。 They seem to just be 1 more than what i and j count to (verified by trying j < this.jsGalleries[i].buttons.length-1 which alerts "8, 2, 2"). 它们似乎仅比ij计数多1(通过尝试j < this.jsGalleries[i].buttons.length-1来警告“ j < this.jsGalleries[i].buttons.length-1 ”)。

Edit : someId , someGallery , and someIndex are not real variables, they are junk I made up to try to explain the problem. 编辑someIdsomeGallerysomeIndex不是真正的变量,它们是我试图解释问题的垃圾someIndex

This is a classic JS mistake. 这是经典的JS错误。 The problem is that the values of i and j are not captured in any function scope, and your event handlers are asynchronous. 问题在于,在任何函数作用域中都不会捕获ij的值,并且事件处理程序是异步的。 That means that when your event handler runs, both for loops have run to completion, thus i == this.jsGalleries.length and j === this.jsGalleries[this.jsGalleries.length - 1].buttons.length . 这意味着当您的事件处理程序运行时,两个for循环都已完成,因此i == this.jsGalleries.lengthj === this.jsGalleries[this.jsGalleries.length - 1].buttons.length

Try out one of these: 尝试以下方法之一:

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);
    }
  }
}

Or possibly clearer: 或更清晰:

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);
    }
  }
}

It's not a problem with addEventListener. addEventListener没问题。 This is a common mistake. 这是一个常见的错误。 In order to understand what's going on, I have to explain how closures work. 为了了解发生了什么,我必须解释闭包是如何工作的。

When you have a loop and a function inside of it: 当您有一个循环和其中的一个函数时:

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

Each function is given a reference to the variable i . 每个函数都引用了变量i That means that they don't retain the value of i at the time you defined them. 这意味着它们在您定义它们时不会保留i的值。 Again, I'll restate, each function has a reference to the same variable i , not to the value that it had at the time the function was declared. 再次重申一下,每个函数都引用了相同的变量i ,而不是该函数声明时的值。 In my example above, all of the setTimeout's are defined asynchronously. 在上面的示例中,所有setTimeout都是异步定义的。 The anonymous functions all fire at 100 milliseconds and each one logs the value that's in i at the time that the function was run. 匿名函数都会在100毫秒后触发,每个匿名函数都会记录该函数运行时i中的值。 In my example, that value would be -1 for all the functions. 在我的示例中,所有函数的该值为-1。

There are 2 ways to solve this. 有两种解决方法。 I'll show you the easy one first: 首先,我将向您展示简单的方法:

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);
        });
    }
}

Here, you're storing the values on the actual DOM element. 在这里,您将值存储在实际的DOM元素上。 These values are equivalent to the values at the time that the loop was run, so the event listener grabs the correct value. 这些值等于运行循环时的值,因此事件侦听器将获取正确的值。 Notice I nested the value in an object called gallery. 注意,我将值嵌套在一个名为gallery的对象中。 I did this to kind of namespace it. 我这样做是为了给它命名空间。 It's not a good idea to store values on elements in the DOM, just in case browsers end up implementing a property with the same name. 将值存储在DOM中的元素上不是一个好主意,以防万一浏览器最终实现了具有相同名称的属性。 I feel like gallery is safe enough. 我觉得画廊足够安全。

The other option, and probably the best practice, for fixing this is to use closures to your advantage. 解决此问题的另一种方法(可能是最佳实践)是使用闭包来发挥自己的优势。

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));
    }
}

In this case, we create a self executing function (called closure in my example) which runs immediately when we're creating the listener. 在这种情况下,我们创建一个自我执行函数(在我的示例中称为closure),该函数在创建侦听器时立即运行。 Let me state it again, this function runs the moment the listener is being added, NOT when it's run. 让我再次声明一下,此函数在添加侦听器的那一刻开始运行,而不是在运行时运行。 The reason we do this is so we can pass in the values we want to save for later, in this case, self, i, and j. 这样做的原因是,这样我们就可以传入想要保存以供以后使用的值,在这种情况下为self,i和j。 Then, when the event occurs, the function that ACTUALLY gets run is the inner function (called actualListener). 然后,当事件发生时,实际运行的函数是内部函数(称为ActualListener)。 actualListener has a copy of all the values stored in its closure at the time that the closure function was run. ActualListener具有运行闭包函数时存储在其闭包中的所有值的副本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM