如何在回调期间从中删除事件侦听器

时间:2016-10-03 18:56:25

标签: javascript audio popcornjs

我有一个带有一些javascript的页面,用于收听一系列音频文件中的故事。我使用爆米花库为特定时间戳添加脚注,这些时间戳突出显示音频播放时页面上文本中的单词。我有一个javascript对象AudioPlayer(本例中的实例称为ap),它包含许多音频元素和许多爆米花实例。第一次音频元素有“加载元数据”时,如果我有该项目的时间戳,我想创建爆米花脚注。但是,我不想为给定的音频项目多次运行此功能。也就是说,人们可以点击某个项目并重新加载它,但我不想重新创建时间戳。出于这个原因,我写了这样的回调代码,当我得到给定项目的时间戳时运行:

// try to load any timestamps for this file
this.loadTS = function(ann) { 
  var xhr = new XMLHttpRequest();
  xhr.open("GET", window.location.protocol+"//"+
                  window.location.hostname+"/sitename/timestamps/"+
                  window.location.pathname.substring(
                  window.location.pathname.lastIndexOf('/')+1)+".json",
            true);
  xhr.onreadystatechange=function(){
    if(xhr.readyState==4 && xhr.status==200){
      console.log(xhr.responseText);
      this.timestamps = JSON.parse(xhr.responseText);
      for(var idx in this.timestamps){

        var stampX = function(){ 
          // this is an audio element, get it's index to 
          // do the stamping  
          var x = window.ap.audios.indexOf(this);

          // need to remove this listner so it doesn't fire again!          
          this.removeEventListener('loadedmetadata',stampX); // <- fail :(
          // window.ap.audios[x]
          //  .removeEventListener('loadedmetadata',stampX);
          // ^^ this failed too :( :(

          // stamp away!
          window.ap.stampItem(window.ap.winIGTs[x], 
            window.ap.timestamps[x], window.ap.audios[x],
              window.ap.popcorns[x]);

        };

        this.audios[idx].addEventListener('loadedmetadata', stampX);

        if(ann) 
          this.textIGTs[idx].setAttribute("class","igt existstamps");
      }
    } else console.log(xhr.status);
  }.bind(this);
  xhr.send();
}

但我在测试此代码时发现了&#39; stampX&#39;如果重新加载音频元素,则会再次调用,所以我唯一的结论是removeEventListener在某种程度上没有得到与下面的addEventListener相同的引用。

我发现这很难调试。我无法将变量传递给stampX,因为需要事件监听器的函数引用(而不是函数调用)。

无论如何,我很难找到正确的方法来编写这个,这样我就可以在第一次调用stampX时删除eventListener。

2 个答案:

答案 0 :(得分:1)

看起来每次都会重新创建stampX,因为你在onreadystatechange函数中声明了它。

这意味着您执行的每个addEventListener都会获得不同的回调函数。

你需要做的是分离它的逻辑,将它移到外面,在词法范围上更高,例如你声明xhr对象或甚至在loadTS减速之外。这样,addEventListener和removeEventListener都将指向相同的函数。

答案 1 :(得分:0)

编辑:看到上面的答案,我在下面写下之后接受了正确的解释。我会离开这个,因为它在问题的背景下举例说明了解决方案。

我有一个有效的解决方案,但我仍然希望更多地了解原因。

解决这个问题的原因是将stampX移动到window.ap对象命名空间,在xhr.onreaystatechange的回调之外。

  this.stampX = function(e) {
    // this is an audio element, get it's index to do the stamping
    var x  = window.ap.audios.indexOf(e.target); 
    // need to remove this listner so it doesn't fire again!  
    this.audios[x].removeEventListener('loadedmetadata',this.stampX);

    this.stampItem(this.winIGTs[x], this.timestamps[x], this.audios[x], 
      this.popcorns[x]); 

  }.bind(this);

  // try to load any timestamps for this file
  this.loadTS = function(ann) {       
    var xhr = new XMLHttpRequest();
    xhr.open("GET", window.location.protocol+"//"+
                    window.location.hostname+"/sitename/timestamps/"+      
                    window.location.pathname.substring(
                    window.location.pathname.lastIndexOf('/')+1)+".json",      
              true);
    xhr.onreadystatechange=function(){
      if(xhr.readyState==4 && xhr.status==200){
        console.log(xhr.responseText);
        this.timestamps = JSON.parse(xhr.responseText);
        for(var idx in this.timestamps){      
          this.audios[idx].addEventListener('loadedmetadata', this.stampX);
          if(ann)       
            this.textIGTs[idx].setAttribute("class","igt existstamps");      
        }
      } else console.log(xhr.status);
    }.bind(this);
    xhr.send();      
  }

现在该功能只调用一次,而且爆米花工作得很好。尽管如此,仍然喜欢听到有关上述错误的评论。

相关问题