Всем привет!
Как и все, кто волею судьбы и руководства изучает JavaScript, столкнулся с передачей параметров в addEventListener
.
Проблема легко решается с помощью .bind
:
handler = function(contextParameter){
console.log(contextParameter);
}
var f = handler.bind(this, context);
parentDiv.addEventListener('click', f('Hello Wolrd!'));
Однако возникает другая проблема, когда создаёшь в цикле несколько объектов и хочешь удалить этот Listener.
Не могу сослаться внутри handler на саму функцию внутри removeEventListener
, чтобы удалить её на(...)конец.
По-быстрому придумал костыли - заношу созданные переменные в массив a_f
, и в .bind передаю текущий i
Далее, удаляю таким макаром:
o_parentToPass.removeEventListener('click', a_f[v_na]);
Где v_na
- параметр, передаваемый в handler, соответствующий i
Помогите, плиз, как сделать всё красиво! Перелистал уже всю мозилу и w3, ничего подходящего не могу найти(((
Как мне передать саму переменную f
внутри f = handler.bind(this, o_context, i);
По подобию:
f = handler.bind(this, o_context, this);
Или f = handler.bind(this, o_context, f);
Эти два варианта сверху не работают (
Вот код:
var a_f = [];
var handler = function(o_parentToPass, v_na){
if(o_parentToPass.classList.contains('test-rotate')){
o_parentToPass.classList.remove('test-rotate');
}else{
o_parentToPass.classList.add('test-rotate');
}
o_parentToPass.removeEventListener('click', a_f[v_na]);
console.log(v_na);
}
var f_createDiv = function(){
for(var i = 0; i < 3; i++){
var o_parent = document.createElement('div');
o_parent.classList.add('parent');
var o_picture = document.createElement('div');
o_picture.classList.add('child', 'picture');
var o_cover = document.createElement('div');
o_cover.classList.add('child', 'cover', 'rotate-cover');
var vs_text = document.createElement('p');
vs_text.innerHTML = i;
o_parent.appendChild(o_picture);
o_parent.appendChild(o_cover);
o_parent.appendChild(vs_text);
var o_context = o_parent;
var f = handler.bind(this, o_context, i);
a_f.push(f);
o_parent.addEventListener('click', f);
document.body.appendChild(o_parent);
}
}
Вариант с
for (var i=0; i<3; i++) {
const node = ...;
const handler = function() {
// ...
node.removeEventListener("click", handler);
}
node.addEventListener("click", handler);
}
Не совсем подходит. Т.к. handler в моём случае - внешний. Его нельзя разместить внутри цикла.
Короче - лучший вариант "прикрутить" к вызываемому методу добавление listener с помощью calm
передавать ноду через bind
и добавлять listener уже в самом методе.
Самый простой вариант - через замыкание. Просто создаем переменную и на нее ссылаемся:
for (var i=0; i<3; i++) {
const node = ...;
const handler = function() {
// ...
node.removeEventListener("click", handler);
}
node.addEventListener("click", handler);
}
Если вы пишите для браузеров которые не поддерживают let и const - то можно использовать еще одно замыкание чтобы разделить области видимости:
for (var i=0; i<3; i++)
!function(){
var node = ...;
var handler = function() {
// ...
node.removeEventListener("click", handler);
}
node.addEventListener("click", handler);
}();
Здесь я использовал IIFE (Immediately Invoked Function Expression) - немедленно вызываемое функциональное выражение. Этот трюк позволяет создать на каждой итерации цикла новые переменные node и handler, без него бы метод removeEventListener всегда отписывал последний обработчик события а не текущий.
Если прошлый пример кажется слишком магическим - вот то же самое, но без IIFE:
for (var i=0; i<3; i++)
step(i);
function step(i) {
var node = ...;
var handler = function() {
// ...
node.removeEventListener("click", handler);
}
node.addEventListener("click", handler);
}
Вариант f = handler.bind(this, o_context, f)
не работает, потому что сначала выполняется правая часть выражения. А значит в bind
передается старое значение f
(или undefined
на первой итерации цикла).
Мой вариант для вашего случая, функция handler
отдельно от основного кода, и вы можете удалять обработчик, когда захотите. Надеюсь код достаточно простой для понимания :)
function handler(element, parameter, getEventHandler) {
element.innerHTML += parameter;
if (true) // ваше условие
element.removeEventListener('click', getEventHandler());
}
for (var i = 0; i < 3; i++) {
var div = document.createElement('div');
div.innerHTML = i;
(function () {
var bindedHandler;
var getBindedHandler = function () { return bindedHandler; }
bindedHandler = handler.bind(this, div, '-clicked-' + i, getBindedHandler);
div.addEventListener('click', bindedHandler);
})();
document.body.append(div);
}
Если задача требудет выполнения обработчика только 1 раз, можно испольвазть третий аргумент addEventListener
. Мы указываем браузеру, что обработчик должен вызваться только 1 раз, а потом браузер сам удалит его:
for (var i = 0; i < 3; i++) {
var div = document.createElement('div');
div.innerHTML = i;
div.addEventListener('click', function () {
this.innerHTML += '-clicked';
}, { once: true });
document.body.append(div);
}
TL;DR:
Достаточно сделать так:
<element>.addEventListener(<eventName>, { handleEvent: <handlerFunction> });
, без всякого излишнего секса с bind()
или замыканием функции.
Пояснение:
Аргументом в addEventListener
можно передавать не только функцию, но и объект соответствующий интерфейсу EventListener
, насовав все необходимые Вам параметры в его (объекта) поля. Внутри функции-листенера, этот объект будет доступен через this
.
Пример:
Сниппет ниже, демонстрирует создание в цикле элементов, реагирующих на клик увеличением их значения на рандомную величину от 1 до 25 включительно. Как только значение достигает 50, слушатель события удаляется.
Чтобы было нагляднее, каждый добавляемый объект слушателя содержит три поля:
handleEvent
- непосредственно обработчик события. В данном случае, это обычная именованная функция из глобальной области видимости;
el
- элемент, владеющий слушателем события (можно не передавать, так как в обработчике доступно свойство target
аргумента Event
. То есть, вместо this.el
можно юзать evt.target
);
rndIncr
- референс функции, генерирующей случайную величину инкремента.
for (let elem, evtHandler, i = 0; i < 10; i++) {
elem = document.createElement('span');
elem.classList.add('test');
elem.textContent = '0';
evtHandler = {
el: elem,
rndIncr: rndIncrement,
handleEvent: clickHandler
};
elem.addEventListener('click', evtHandler);
document.body.appendChild(elem);
}
function clickHandler(evt) {
let i = this.rndIncr(),
newVal = +this.el.textContent + i;
console.clear();
console.log('Случайный инкремент: ' + i);
this.el.textContent = String(newVal);
if (newVal >= 50) {
this.el.style.boxShadow = '0 0 0 1px #ccc';
this.el.removeEventListener('click', this);
}
}
function rndIncrement() {
return Math.floor(Math.random() * 25) + 1;
}
.test {
display: inline-block;
margin: 5px;
width: 50px; line-height: 50px;
text-align: center;
user-select: none;
box-shadow: 0 2px 5px 0 #0004;
transition: box-shadow 0.3s ease-out;
}
Почему это важно, и чем это (имхо) лучше:
Во-первых, такой "объектный" подход читабельнее по коду:
«вот объект с такими-то полями, и вот мы его передаем»
вместо варианта ф. выражений:
«вот функция, которая создает замыкание, в котором объявлены переменные, которые передаются вложенной функции , в доме который построил Джек»
То есть, решение типа "есть же callback hell, так давайте еще мини function expression hell запилим" - скажем так, сомнительное... Плюс, не будем забывать что вызовы в JavaScript дорогие.
Во-вторых, способ достаточно универсален: он позволяет инкапсулировать обработчики внутри любого объекта, включая объекты являющиеся экземплярами класса. То есть, не обязательно передавать именно литерал объекта - можно использовать фабрику, или конструктор, или добавить поле обработчика в уже созданный экземпляр. Когда в коде активно используются объекты, это будет удобно.
В-третьих, такой подход "более стандартный", что ли... В том смысле, что метод addEventListener
явно предполагает использование объектов во втором аргументе - тогда как использование bind()
или вложенных функций, в данном контексте, смахивает на костыль.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Недавно начал учить Vue и сразу-же застрял на таком моменте
Мне надо проверить были ли изменения при клике в iteminfo0_item_name или в iteminfo1_item_name
Как в phpunit, можно очистить таблицу в тестовой базе после каждого теста? Пытался использовать:lluminate\Foundation\Testing\RefreshDatabase (примочка для тестирования...