Лучший способ обработки кликов на кнопках в JavaScript (ваниль)

Изменить: мой вопрос, возможно, был слишком широким (все еще изучающим), но я все же получил очень полезные ответы. Так что спасибо вам большое за ваш вклад. Я также исправил свой код, чтобы включить передачу "event".


Это мой первый вопрос, заданный здесь, поэтому я немного нервничаю...

Здесь мы идем: Каковы наилучшие способы (относительно лучшей практики, скорости, совместимости и т.д.) для управления щелчками на кнопках и позволяют им запускать разные функции?

При исследовании этого вопроса я нашел пример на Eloquent JavaScript и взял это в качестве основы.

Затем я расширил его, используя "mapper" (это правильный термин?), чтобы найти нужную функцию, в зависимости от идентификатора кнопки, которая запускает событие.

Вот код:

// functions I want to trigger
function one(e) {
  alert("You clicked " + e.textContent);
}

function two(e) {
  alert("You clicked " + e.textContent);
}

// mapper object to hold my functions
var buttonMap = {
  b_one: function(event) {
    one(event.target)
  },
  b_two: function(event) {
    two(event.target)
  }
}

// Event Listener
var buttonClick = document.body.addEventListener('click', function(event) {
  if (event.target.nodeName == "BUTTON") {
    var tar = event.target.id;
    buttonMap[tar](event)
  }
});
<button id="b_one" type="button">Button One</button>
<button id="b_two" type="button">Button Two</button>

Ответ 1

Вы создали простое делегирование событий, которое даст вам возможность добавлять/удалять кнопки без необходимости добавлять/удалять обработчики событий.

Если у вас есть много кнопок, которые вы создаете или уничтожаете динамически, лучше использовать делегирование событий, чем платить за ручное управление обработчиками.

Вы можете поместить код обработки событий в конец своего тела вместо использования события DOMContentLoaded. Пока контейнер (body в этом случае) существует, не имеет значения, существуют ли дети или нет.

Прикрепите глобальный обработчик событий к ближайшему контейнеру, а не к телу, если сможете. Кроме того, используйте атрибуты data-* вместо атрибута id или value, чтобы добавить данные к кнопкам. id создает глобальную переменную, и значение будет использоваться в формах.

<div class="buttonsContainer">
  <button type="button" data-value="one">Button One</button>
  <button type="button" data-value="two">Button Two</button>
</div>

Вы должны передать объект события обработчику событий:

var buttonClick = container && container.addEventListener('click', 
    function(event) {
      var target = event.target;
      var handler;
      if (target.nodeName == "BUTTON" && (handler = target.getAttribute('data-handler'))) {
        buttonMap[handler](event)
      }
    });

Демо:

function one(e) {
  alert("You clicked " + e.textContent);
}

function two(e) {
  alert("You clicked " + e.textContent);
}

// mapper object to hold my functions
var buttonMap = {
  b_one: function(event) {
    one(event.target)
  },
  b_two: function(event) {
    two(event.target)
  }
}

// Event Listener
var container = document.querySelector('.buttonsContainer');
var buttonClick = container && container.addEventListener('click', function(event) {
  var target = event.target;
  var handler;
  if (target.nodeName == "BUTTON" && (handler = target.getAttribute('data-handler'))) {
    buttonMap[handler](event)
  }
});
<div class="buttonsContainer">
  <button type="button" data-handler="b_one">Button One</button>
  <button type="button" data-handler="b_two">Button Two</button>
</div>

Ответ 2

Функция mapper действительно не добавляет к этому ничего, кроме добавления слоя абстракции, который может стать полезным в динамических средах DOM.

Однако, если вы знаете, что в камере, какая функция идет с какой кнопкой, вы можете просто зарегистрировать функции с помощью соответствующих кнопок напрямую.

Кроме того, имейте в виду, что при регистрации анонимной функции в качестве обратного вызова события вы не сможете использовать .removeEventListener() для отмены регистрации обратного вызова позже. Это не означает, что использование анонимных функций для событий является нарушителем транзакций, но это что-то для рассмотрения.

Обратите внимание, что у вас было несколько проблем с HTML и JavaScript, которые объясняются в комментариях:

document.getElementById("b_one").addEventListener("click", one);
document.getElementById("b_two").addEventListener("click", two);

// The e parameter represents the event object itself. It won't
// have any textcontent. But, in a DOM event handler, "this" will
// refer to the element that triggered the event and that where
// the textContent is.
function one(e) {
  alert("You clicked " + this.textContent);
}

function two(e) {
  alert("You clicked " + this.textContent);
}
<!-- You typically won't give a <button> element a "value"
     attribute. The content within the element is usually 
     what you need. -->
<button id="b_one" type="button">Button One</button>
<button id="b_two" type="button">Button Two</button>

Ответ 3

Ваш подход прекрасен, но он не работает так, как он писал прямо сейчас. Ваши отображаемые функции вызовут ваши обработчики с помощью event.target, но вы никогда не проходите event при вызове отображаемой функции. Отображаемые функции должны выглядеть так, чтобы работать:

...
b_one: function(event) {
  one(event.target)
}
...

... и ваше обращение к нему в обработчике событий должно выглядеть так:

buttonMap[tar](event)

Что вы делаете, присоединяя слушателя событий к телу и вызывая обработчик на основе цели события, называется делегированием событий. Вы должны добавить проверку, чтобы убедиться, что нажатая кнопка действительно имеет связанную отображаемую функцию, чтобы убедиться, что она не сработает при нажатии на другую кнопку:

if (typeof buttonMap[tar] === 'function') {
  buttonMap[tar](event);
}

Цель запуска кода в DOMContentLoaded заключается в том, чтобы убедиться, что код работает после того, как DOM был разобран и доступен. Это также может быть достигнуто путем размещения блока script после элементов DOM, на которые он опирается. Например, в конце элемента body.

Ответ 4

Просто для полноты вы также можете использовать атрибут onClick HTML.

function myFunction() {
  alert('hey!');
}
<button onClick="myFunction()">Do something</button>