Как удалить прослушиватель событий из этого во время обратного вызова
У меня есть страница с некоторыми 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();
}
Но при тестировании этого кода я обнаружил, что "stampX" вызывается еще раз, если аудиоэлемент перезагружается, поэтому мой единственный вывод состоит в том, что removeEventListener почему-то не получает ту же ссылку, что и addEventListener ниже.
Я нахожу это трудным для отладки. Я не могу передать переменную в stampX, потому что мне нужна ссылка на функцию для прослушивателя событий (не вызов функции).
В любом случае, мне трудно найти правильный способ написать это так, чтобы я мог удалить eventListener при первом вызове stampX.
2 ответа
Похоже, stampX воссоздается каждый раз, потому что вы объявляете его внутри функции onreadystatechange.
Это означает, что каждый добавленный вами addEventListener получает свою функцию обратного вызова.
Что вам нужно сделать, это отделить его логику, переместить его наружу, на лексическую область выше, например, где вы объявляете объект xhr или даже за пределами замедления loadTS. Таким образом, и addEventListener, и removeEventListener будут указывать на одну и ту же функцию.
РЕДАКТИРОВАТЬ: видел ответ выше по кулак, который я принял как правильный сразу после того, как я написал ниже. Я оставлю это, потому что это иллюстрирует решение в контексте вопроса.
У меня есть решение, которое работает, но я все равно хотел бы узнать больше о том, почему.
Что решило проблему для меня, так это перемещение 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();
}
Теперь функция вызывается только один раз, и попкорны работают хорошо. Тем не менее, я все еще люблю слышать комментарии о том, что было не так.