deleteEventListener про анонімні функції в JavaScript


102

У мене є об'єкт, в якому є методи. Ці методи поміщаються в об'єкт всередині анонімної функції. Це виглядає приблизно так:

var t = {};
window.document.addEventListener("keydown", function(e) {
    t.scroll = function(x, y) {
        window.scrollBy(x, y);
    };
    t.scrollTo = function(x, y) {
        window.scrollTo(x, y);
    };
});  

(код набагато більше, але цього достатньо, щоб показати проблему)

Тепер я хочу зупинити слухача подій у деяких випадках. Тому я намагаюся зробити removeEventListener, але я не можу зрозуміти, як це зробити. Я в інших питаннях читав, що неможливо викликати deleteEventListener на анонімних функціях, але чи так це в цій ситуації?

У мене є метод t, створений всередині анонімної функції, і тому я вважав, що це можливо. Виглядає так:

t.disable = function() {
    window.document.removeEventListener("keydown", this, false);
}

Чому я не можу цього зробити?

Чи існує інший (хороший) спосіб зробити це?

Інформація про бонуси; це має працювати лише в Safari, отже, відсутня підтримка IE.


Чому цю функцію не зберегти? Обробник подій може бути не анонімною функцією.
kirilloid

2
Я усвідомлюю, що це трохи пізно, але ви також можете використовувати методи Node.setUserData /Node.getUserData для зберігання даних про елемент. Наприклад, коли вам потрібно встановити анонний прослуховувач (і мати можливість його видалити), спочатку встановіть для (Elem.setUserData('eventListener', function(e){console.log('Event fired.');}, null);userdata функцію anon, а потім виконайте Elem.addEventListener ('event', Elem.getUserData ('eventListener'), false); ... і те саме для removeEventListener. Сподіваюся, ви зможете побачити це добре.
Чейз

EDIT: Згідно з попереднім коментарем, я думаю, це працює лише у Firefox ... Я щойно спробував IE8 (IE9 невідомий), Safari 5.1.2, Chrome (?), Opera 11 .. Без кісток
Чейз

Відповіді:


77

Я вважаю, що це сенс анонімної функції, їй бракує імені чи способу посилання на неї.

Якби я був на вашому місці, я б просто створив іменовану функцію або помістив її у змінну, щоб у вас було посилання на неї.

var t = {};
var handler = function(e) {
    t.scroll = function(x, y) {
        window.scrollBy(x, y);
    };
    t.scrollTo = function(x, y) {
        window.scrollTo(x, y);
    };
};
window.document.addEventListener("keydown", handler);

Потім ви можете видалити його

window.document.removeEventListener("keydown", handler);   

3
Дякуємо за відповідь. Я пішов із: var handler; window.document.addEventListener ("keydown", handler = function (e) {Але я не розумію, чому "this" не посилається на прослуховувач подій. Чи не повинен прослуховувач подій бути об'єктом?
bitkid

1
thisКлючове слово може збивати з пантелику. Хороше місце для читання - quirksmode.org/js/this.html
Адам Хіт

Велике спасибі. Це було дуже корисно.
bitkid

Я намагаюся зробити це, щоб заблокувати дійсно стійку рекламу на веб-сайті. Я знаю, що це сенс анонімних функцій, але це не означає, що я не хотів би знати, як це робити.
Wyatt8740,

@bitkid: Усередині функції обробника (припускаючи, що це не функція стрілки), посилання thisвідноситься на елемент, до якого додано слухач, а не на саму подію (яка буде параметром e). Тому this === e.currentTarget. прочитати developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
chharvey

100

якщо ви перебуваєте всередині фактичної функції, ви можете використовувати аргументи.callee як посилання на функцію. а саме:

button.addEventListener('click', function() {
      ///this will execute only once
      alert('only once!');
      this.removeEventListener('click', arguments.callee);
});

EDIT: Це не спрацює, якщо ви працюєте в суворому режимі ( "use strict";)


2
Це приємно, оскільки воно зберігає переваги анонімних функцій (не забруднює простір імен тощо).
bompf

3
спробував це в програмі WinJS, отримав наступну помилку: "Доступ до властивості" callee "об'єкта аргументів не дозволяється в суворому режимі"
Валентин Кантор,

1
@ValentinKantor: Це тому, що щось у коді є "використовувати строго"; , і ви не можете використовувати виклик у суворому режимі.
OMA

19
Дайте ім’я функції inline, і ви можете посилатися на неї, не вдаючись до argument.callee:button.addEventListener('click', function handler() { this.removeEventListener('click', handler); });
Harry Love

4
Як зазначається в Mozilla: "Попередження: 5-е видання ECMAScript (ES5) забороняє використовувати argument.callee () в суворому режимі. Уникайте використання аргументів.callee (), надаючи ім'я виразів функції або використовуйте декларацію функції, де функція мусить покликати себе ».
чувак,

50

Варіант рішення Отто Наскарелли , який працює в суворому режимі:

button.addEventListener('click', function handler() {
      ///this will execute only once
      alert('only once!');
      this.removeEventListener('click', handler);
});

4
Прекрасне рішення!
Ерік Норкросс,

2
Це може бути не правильним шляхом, але це найпростіше.
Віньєш

7
window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));  

Може бути кілька анонімних функцій, клавіатура 1

Попередження: працює лише в Chrome Dev Tools& не може використовуватися в коді : посилання


2
Дякую, ви вирішили загадку, принаймні в Chrome, за те, що багато жартівників сказали, що неможливо. Людина, ти як ... Бетмен!
JasonXA

20
getEventListenersздається частиною інструментів Chrome Dev, тому насправді не може бути використаний ні для чого, крім налагодження.
DBS

1
Щойно спробував, підтвердив, що він доступний лише в Devtools, а не в тому випадку, якщо він включений у сценарій всередині сторінки.
Андрес Ріофріо

5

в сучасних браузерах ви можете зробити наступне ...

button.addEventListener( 'click', () => {
    alert( 'only once!' );
}, { once: true } );

https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters


Класно, поки не з’ясуєте, що жодна версія IE, ані край <16 насправді не підтримують цю функцію. Принаймні через 5 років ми можемо використовувати це з тих пір, коли IE буде (читай: повинно) бути застарілим, Edge займе своє місце, і він використовуватиме движок webkit замість їх власного "EdgeHTML".
SidOfc

1
з цим поліфілом для записів DOM рівня 4 ви повинні бути добре npmjs.com/package/dom4
shunryu111

2

Не такий анонімний варіант

element.funky = function() {
    console.log("Click!");
};
element.funky.type = "click";
element.funky.capt = false;
element.addEventListener(element.funky.type, element.funky, element.funky.capt);
// blah blah blah
element.removeEventListener(element.funky.type, element.funky, element.funky.capt);

Оскільки отримав відгук від Енді ( цілком правильно, але, як і на багатьох прикладах, я хотів показати контекстне розширення ідеї ), ось менш складний виклад:

<script id="konami" type="text/javascript" async>
    var konami = {
        ptrn: "38,38,40,40,37,39,37,39,66,65",
        kl: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
    };
    document.body.addEventListener( "keyup", function knm ( evt ) {
        konami.kl = konami.kl.slice( -9 );
        konami.kl.push( evt.keyCode );
        if ( konami.ptrn === konami.kl.join() ) {
            evt.target.removeEventListener( "keyup", knm, false );

            /* Although at this point we wish to remove a listener
               we could easily have had multiple "keyup" listeners
               each triggering different functions, so we MUST
               say which function we no longer wish to trigger
               rather than which listener we wish to remove.

               Normal scoping will apply to where we can mention this function
               and thus, where we can remove the listener set to trigger it. */

            document.body.classList.add( "konami" );
        }
    }, false );
    document.body.removeChild( document.getElementById( "konami" ) );
</script>

Це дозволяє ефективно анонімну структуру функцій, уникає використання практично застарілого виклику та дозволяє легко видалити.

До речі : вилучення елемента сценарію відразу після налаштування слухача - це симпатична хитрість для приховування коду, який би вважав за краще не зовсім очевидний для сторонніх очей ( зіпсував би сюрприз ;-)

Отже, метод ( простіше ):

element.addEventListener( action, function name () {
    doSomething();
    element.removeEventListener( action, name, capture );
}, capture );

2
Це надто складно.
Ben Sinclair,

@Andy Я згоден, начебто, але намагався показати, що просто немає можливості видалити анонімну функцію. Він повинен певним чином посилатися (навіть callee (це погано, M'Kay) посилається на функцію), і таким чином надає приклад лише одного (іншого) способу, на який може посилатися функція - і, як вона побудована з частин, які можна однаково зберігати для подальшого посилання (важлива частина). Очевидно, що справді анонімна функція дещо побудована на ходу, і тому пізніше знаючи, яка дія / тип події та чи було використано захоплення, також слід знати. У будь-якому разі, ось кращий метод :-)
Фред Гандт,

У мене чудово працювало. Я не міг побачити іншого способу передачі аргументів у функцію, оскільки вона не могла бути анонімною.
nicodemus13

2

Це не ідеально, оскільки це видаляє все, але може працювати для ваших потреб:

z = document.querySelector('video');
z.parentNode.replaceChild(z.cloneNode(1), z);

Клонування вузла копіює всі його атрибути та їх значення, включаючи внутрішні прослуховувачі. Він не копіює слухачів подій, доданих за допомогою addEventListener ()

Node.cloneNode ()


Це абсолютно блискуче
Ахмад Алфі

1

JavaScript : метод addEventListener реєструє вказаний прослуховувач у EventTarget (Елемент | документ | Вікно), до якого він викликаний.

EventTarget. addEventListener ( тип_події , функція_обробника, Bubbling | Захоплення );

Миша, події клавіатури Приклад тесту в WebConsole:

var keyboard = function(e) {
    console.log('Key_Down Code : ' + e.keyCode);
};
var mouseSimple = function(e) {
    var element = e.srcElement || e.target;
    var tagName = element.tagName || element.relatedTarget;
    console.log('Mouse Over TagName : ' + tagName);    
};
var  mouseComplex = function(e) {
    console.log('Mouse Click Code : ' + e.button);
} 

window.document.addEventListener('keydown',   keyboard,      false);
window.document.addEventListener('mouseover', mouseSimple,   false);
window.document.addEventListener('click',     mouseComplex,  false);

Метод removeEventListener видаляє прослуховувач подій, раніше зареєстрований у EventTarget.addEventListener ().

window.document.removeEventListener('keydown',   keyboard,     false);
window.document.removeEventListener('mouseover', mouseSimple,  false);
window.document.removeEventListener('click',     mouseComplex, false);

канюза


1

Щоб надати більш сучасний підхід до цього:

//one-time fire
element.addEventListener('mousedown', {
  handleEvent: function (evt) {
    element.removeEventListener(evt.type, this, false);
  }
}, false);

2
Пояснення було б добре.
Poul Bak

1

Я натрапив на ту ж проблему, і це було найкращим рішенням, яке я міг отримати:

/*Adding the event listener (the 'mousemove' event, in this specific case)*/
element.onmousemove = function(event) {
    /*do your stuff*/
};
/*Removing the event listener*/
element.onmousemove = null;

Будь ласка, майте на увазі, я перевірив це лише на windowелемент та на 'mousemove'подію, тому з цим підходом можуть виникнути проблеми.


0

Можливо, не найкраще рішення з точки зору того, що ви просите. Я досі не визначив ефективного методу для видалення анонімної функції, оголошеної в рядку, із викликом слухача події.

Я особисто використовую змінну для зберігання <target>та оголошення функції поза викликом прослуховувача подій, наприклад:

const target = document.querySelector('<identifier>');

function myFunc(event) { function code; }

target.addEventListener('click', myFunc);

Потім, щоб видалити слухача:

target.removeEventListener('click', myFunc);

Не найкраща рекомендація, яку ви отримаєте, але видалення анонімних функцій єдиним рішенням, яке я знайшов корисним, є видалення та заміна елемента HTML. Я впевнений, що повинен бути кращий метод ванільного JS, але я його ще не бачив.


0

Я знаю, що це досить стара тема, але думав, що я міг би вкласти свої два центи для тих, хто вважає це корисним.

Сценарій (вибачення за назви нетворчих методів):

window.Listener = {
    _Active: [],
    remove: function(attached, on, callback, capture){
        for(var i = 0; i < this._Active.length; i++){
            var current = this._Active[i];
            if(current[0] === attached && current[1] === on && current[2] === callback){
                attached.removeEventListener(on, callback, (capture || false));
                return this._Active.splice(i, 1);
            }
        }
    }, removeAtIndex(i){
        if(this._Active[i]){
            var remove = this._Active[i];
            var attached = remove[0], on = remove[1], callback = remove[2];
            attached.removeEventListener(on, callback, false);
            return this._Active.splice(i, 1);
        }
    }, purge: function(){
        for(var i = 0; i < this._Active.length; i++){
            var current = this._Active[i];
            current[0].removeEventListener(current[1], current[2]);
            this._Active.splice(i, 1);
        }
    }, declare: function(attached, on, callback, capture){
        attached.addEventListener(on, callback, (capture || false));
        if(this._Active.push([attached, on, callback])){
            return this._Active.length - 1;
        }
    }
};

І ви можете використовувати його так:

// declare a new onclick listener attached to the document
var clickListener = Listener.declare(document, "click" function(e){
    // on click, remove the listener and log the clicked element
    console.log(e.target);
    Listener.removeAtIndex(clickListener);
});

// completely remove all active listeners 
// (at least, ones declared via the Listener object)
Listener.purge();

// works exactly like removeEventListener
Listener.remove(element, on, callback);

-4
window.document.onkeydown = function(){};

Чому ні = не визначено? Справжній поганий.
Бен Сінклер

2
Це не видалить жодного обробника, зареєстрованого в addEventListener.
Luc125
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.