如何在回调期间从此删除事件侦听器
how to remove event listener from this during callback
我有一个页面有一些 javascript 用于收听一系列音频文件中的一些故事。我使用爆米花库为特定时间戳添加脚注,在播放音频时突出显示页面上文本中的单词。我有一个 javascript 对象 AudioPlayer(本例中的实例称为 ap),它包含许多音频元素和许多爆米花实例。音频元素第一次具有 'loadedmetadata' 时,如果我有该项目的时间戳,我想创建爆米花脚注。但是,对于给定的音频项目,我不想 运行 此函数多次。也就是说,人们可能会点击一个项目并重新加载它,但我不想重新创建时间戳。出于这个原因,当我获得给定项目的时间戳时,我将这样的回调代码写到 运行:
// 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();
}
但我在测试这段代码时发现,如果音频元素被重新加载,'stampX' 会再次被调用更多,所以我唯一的结论是 removeEventListener 不知何故没有获得与下面的 addEventListener 相同的引用。
我发现这很难调试。我无法将变量传递给 stampX,因为需要事件侦听器的函数引用(不是函数调用)。
无论如何,我很难找到正确的编写方法,以便我可以在第一次调用 stampX 时删除 eventListener。
看起来每次都会重新创建 stampX,因为您在 onreadystatechange 函数中声明了它。
这意味着您所做的每个 addEventListener 都会获得不同的回调函数。
你需要做的是分离它的逻辑,把它移到外面,在词法范围内更高的地方,例如你声明 xhr 对象的地方,甚至在 loadTS 减速之外。这样 addEventListener 和 removeEventListener 将指向同一个函数。
编辑:看到 fistuks 上面的答案,我在下面写完后就接受了它是正确的。我将保留它,因为它在问题的上下文中举例说明了解决方案。
我有一个有效的解决方案,但我仍然很想知道更多原因。
解决我问题的方法是将 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();
}
现在函数只调用一次,爆米花效果很好。不过,仍然喜欢听到关于上面错误的评论。
我有一个页面有一些 javascript 用于收听一系列音频文件中的一些故事。我使用爆米花库为特定时间戳添加脚注,在播放音频时突出显示页面上文本中的单词。我有一个 javascript 对象 AudioPlayer(本例中的实例称为 ap),它包含许多音频元素和许多爆米花实例。音频元素第一次具有 'loadedmetadata' 时,如果我有该项目的时间戳,我想创建爆米花脚注。但是,对于给定的音频项目,我不想 运行 此函数多次。也就是说,人们可能会点击一个项目并重新加载它,但我不想重新创建时间戳。出于这个原因,当我获得给定项目的时间戳时,我将这样的回调代码写到 运行:
// 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();
}
但我在测试这段代码时发现,如果音频元素被重新加载,'stampX' 会再次被调用更多,所以我唯一的结论是 removeEventListener 不知何故没有获得与下面的 addEventListener 相同的引用。
我发现这很难调试。我无法将变量传递给 stampX,因为需要事件侦听器的函数引用(不是函数调用)。
无论如何,我很难找到正确的编写方法,以便我可以在第一次调用 stampX 时删除 eventListener。
看起来每次都会重新创建 stampX,因为您在 onreadystatechange 函数中声明了它。
这意味着您所做的每个 addEventListener 都会获得不同的回调函数。
你需要做的是分离它的逻辑,把它移到外面,在词法范围内更高的地方,例如你声明 xhr 对象的地方,甚至在 loadTS 减速之外。这样 addEventListener 和 removeEventListener 将指向同一个函数。
编辑:看到 fistuks 上面的答案,我在下面写完后就接受了它是正确的。我将保留它,因为它在问题的上下文中举例说明了解决方案。
我有一个有效的解决方案,但我仍然很想知道更多原因。
解决我问题的方法是将 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();
}
现在函数只调用一次,爆米花效果很好。不过,仍然喜欢听到关于上面错误的评论。