[JavaScript] 事件委託以及 Vue 列表迴圈事件繫結的效能最佳化

Himmelbleu發表於2024-09-11

前言

事件委託(Event Delegation) 是一種透過將事件監聽器繫結到父元素,而不是直接繫結到每個子元素上的技術。這樣可以減少事件監聽器的數量,提升效能,並使得對動態新增或移除的元素更容易進行事件處理。

事件冒泡和事件捕獲

事件冒泡:從裡往外

<div id="parent" style="padding: 50px; background-color: lightblue">
  Parent Div
  <button id="child" style="padding: 20px">Child Button</button>
</div>

<script>
  const parentDiv = document.getElementById("parent");
  const childButton = document.getElementById("child");

  parentDiv.addEventListener(
    "click",
    event => {
      console.log("Parent Div Clicked (Capturing)");
    }
  );

  childButton.addEventListener("click", () => {
    console.log("Button Clicked");
  });
</script>

事件冒泡:從裡往外

事件捕獲:從外往裡

const parentDiv = document.getElementById("parent");
const childButton = document.getElementById("child");

parentDiv.addEventListener(
  "click",
  event => {
    console.log("Parent Div Clicked (Capturing)");
  },
  true
); // 捕獲階段處理

childButton.addEventListener("click", () => {
  console.log("Button Clicked");
});

事件捕獲:從外往裡

事件委託

事件委託主要是依賴於事件冒泡(事件從最深層的子元素冒泡到父元素)。透過將事件監聽器繫結到父元素,當子元素觸發事件時,事件會冒泡到父元素並在父元素上捕捉到事件。我們可以透過 event.target 來判斷事件最初是由哪個子元素觸發的。

事件監聽器繫結到 li 的父元素 ul 身上,點選其子元素 li,就可以觸發點選事件。點選子元素會依次往上冒泡,直到 ul 的事件監聽器捕獲到點選事件。

<button id="btn">點選新增標籤項</button>
<ul id="ul">
  <li>標籤項</li>
  <li>標籤項</li>
  <li>標籤項</li>
</ul>

<script>
  const btn = document.querySelector("btn");
  const ul = document.querySelector("#ul");

  ul.addEventListener("click", function (e) {
    console.log(e.target);
  });
</script>

Vue 列表迴圈效能最佳化

當初透過 B站 網上影片學習 Vue,對於這類需求影片中直接把事件繫結給 li,直到了解到事件委託和事件冒泡,才知道這樣做對效能有很大影響。

v-for 迴圈中將索引 index 繫結到 <li> 元素的 data- 屬性中,然後在事件委託的處理函式中透過 event.target 獲取它。

<template>
  <ul @click="handleClick">
    <li v-for="(item, index) in items" :key="index" :data-index="index">
      {{ item }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: ['Item 1', 'Item 2', 'Item 3'],
    };
  },
  methods: {
    handleClick(event) {
      // 判斷點選的目標是否為 <li> 元素
      if (event.target.tagName === 'LI') {
        // 透過自定義屬性 data-index 獲取對應的索引
        const index = event.target.dataset.index;
        console.log('Clicked item index:', index);
        console.log('Clicked item value:', this.items[index]);
      }
    }
  }
}
</script>

相關文章