处理 JavaScript 中按钮点击的最佳方式(香草)

Best way to handle clicks on buttons in JavaScript (vanilla)

编辑:我的问题可能过于宽泛(仍在学习),但我仍然收到了非常有用的答案。非常感谢大家的意见。我还更正了我的代码以合并传递 'event'.


这是我在这里问的第一个问题,所以我有点紧张...

开始:管理按钮点击并让它们触发不同功能的最佳方法是什么(关于最佳实践、速度、兼容性等)?

在研究这个问题时,我在Eloquent JavaScript上找到了一个例子,并以此为基础。

然后我扩展了它,通过使用“映射器”(这是正确的术语吗?)根据触发事件的按钮的 ID 找到正确的功能。

代码如下:

// 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>

这个方案可以吗?如何改进?我可以 运行 解决任何问题吗?

小红利问题:此外,我考虑过将整个内容包装在 document.addEventListener("DOMContentLoaded", function() {}) 中。

这是个好主意还是有必要?在什么情况下我应该检查 DOM 内容是否已加载,什么时候可以省略此检查?

映射器函数实际上并没有为此添加太多内容,只是添加了可能在动态 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's 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>

现在,如果您的回调函数需要参数,您需要将其包装在另一个函数中,该函数成为主要回调函数,然后该函数可以调用您的实际函数,向它发送它可能需要的参数,如下所示:

document.getElementById("b_one").addEventListener("click", function(evt) {
  // The actual callback will automatically recieve a reference to the
  // event that triggered it, so it's a good idea to pass that along
  // to the main function so that the DOM object and details about the 
  // event can be used along with any other custom arguments you require.
  one(evt, new Date().toLocaleTimeString()); 
});

function one(e, d) {
  console.log("The event was: " + e.type);
  console.log("It happened at: " + d);
  console.log("The text of the element was: " + e.target.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>

您已经创建了一个简单的事件委托,使您能够 add/remove 按钮而无需 add/remove 事件处理程序。

如果您有很多动态创建或销毁的按钮,最好使用事件委托,而不是支付手动管理处理程序的成本。

您可以将事件处理代码放在主体的末尾,而不是使用 DOMContentLoaded 事件。只要容器(本例中为 body)存在,子项是否存在并不重要。

将全局事件处理程序附加到最近的容器,如果可以,不要附加到主体。此外,使用 data-* 属性代替 idvalue 属性向按钮添加数据。 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>

你的方法很好,但它现在的写法并不奏效。您的映射函数使用 event.target 调用您的处理程序,但您在调用映射函数时从不传递 event。映射函数需要看起来像这样才能工作:

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

... 并且您在事件处理程序中对它的调用需要如下所示:

buttonMap[tar](event)

您正在做的,将事件侦听器附加到主体并根据事件目标调用处理程序,称为事件委托。您应该添加一个检查以确保单击的按钮确实具有关联的映射函数,以确保它在单击其他按钮时不会崩溃:

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

运行 代码在 DOMContentLoaded 上的目的是确保代码在 DOM 被解析并可用后运行。这也可以通过将脚本块放在它所依赖的 DOM 元素之后来实现。例如,在 body 元素的末尾。

为了完整起见,您还可以考虑使用 onClick HTML 属性。

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

这可能是过时的做法,但它非常容易理解和维护,这正是现在像 React* 这样的库所发生的事情。

*:嗯,不是真的,但是 API 是相似的。