1. 程式人生 > 實用技巧 >第十一章-js事件

第十一章-js事件

事件繫結和事件冒泡

題目

  • 編寫一個通用的事件監聽函式
  • 描述事件冒泡的流程
  • 無限下拉圖片列表,如何監聽每個圖片的點選

知識點

  • 事件繫結
  • 事件冒泡
  • 事件代理

事件繫結

通過 addEventListener(type, fn) 進行事件繫結,下面為事件繫結函式簡單封裝

// 通用的事件繫結函式
function bindEvent(elem, type, fn) {
  elem.addEventListener(type, fn)
}

const a = document.getElementById('link1')
bindEvent(a, 'click', event => {
  console.log(event.target) // 列印的時當前元素 a
  event.preventDefault() // 阻止預設行為
  alert('clicked')
})

事件冒泡

事件冒泡:基於DOM樹形結構,事件會順著觸發元素向上冒泡。為父元素繫結事件,當觸發子元素時利用事件冒泡機制會觸發父元素繫結的事件。應用場景:事件代理

// 通用的事件繫結函式
function bindEvent(elem, type, fn) {
  elem.addEventListener(type, fn)
}

const body = document.body
// 為 body 繫結事件,當點選body的子元素的時候,會冒泡到body身上,因此會觸發body上面繫結的事件
bindEvent(body, 'click', event => {
  console.log('body clicked')
  console.log(event.target) // 點選的元素
})

// 當點選p1的時候,p1 會冒泡到body上,通過 stopPropagation() 阻止冒泡
const p1 = document.getElementById('p1')
bindEvent(p1, 'click', event => {
  event.stopPropagation() // 阻止冒泡
  console.log('p1 clicked')
})

事件代理

通過冒泡機制,內部元素不繫結事件,而為外層元素繫結事件,從而實現事件代理。應用場景:無限下拉圖片列表,如何監聽每個圖片的點選

  • 程式碼簡潔
  • 減少瀏覽器記憶體的佔用
  • 不要濫用代理
<div id='div1'>
  <a href='#'>a1</a>
  <a href='#'>a2</a>
  <a href='#'>a3</a>
  <a href='#'>a4</a>
  <button>點選增加一個 a 標籤</button>
</div>


<script>
  // 給父元素繫結事件,通過事件冒泡機制,點選子元素時觸發事件
  const div1 = document.getElementById('div1')
  div1.addEventListener('click', event => {
    event.preventDefault() // 阻止a標籤預設行為
    const target = event.target
    if (event.nodeName === 'A') {
      alert(target.innerHTML)
    }
  })
</script>

通用的事件繫結函式(事件代理和普通繫結)

function bindEvent(elem, type, selector, fn) {
  // 如果是普通繫結,只需要傳入三個引數,即把fn設定為selector,selector設定為null
  if (fn == null) {
    fn = selector
    selector = null
  }
  elem.addEventListener(type, event => {
    // 獲取觸發的元素
    const target = event.target
    // 如果有selector說明是代理的情況,並且觸發的元素和傳入的元素相同
    if (selector && target.matches(selector)) {
      fn.call(target, event)
    } else {
      // 普通繫結
      fn.call(target, event)   
    }
  })
}

// 普通繫結 注意箭頭函式的this指向
const btn1 = document.getElementById('btn')
bindEvent(btn1, 'click', function (event) {
  event.preventDefault() // 阻止預設行為
  alert(this.innerHTML)
})

// 代理繫結
const div1 = document.getElementById('div1')
bindEvent(div1, 'click', 'A', function (event) {
  event.preventDefault()
  alert(this.innerHTML)
})

---------------------------下面為補充內容------------------------------

事件捕獲,事件冒泡和事件代理

事件捕獲和事件冒泡都是為了解決頁面中事件流(事件的執行順序)的問題。

<div id='outer'>
  <p id='inner'>click me</p>
</div>

事件捕獲:事件從最外層觸發,直到找到最具體的元素。

如上面的程式碼,在事件捕獲下,如果點選p標籤,click 事件的順序應該是 document->html->body->div->p

事件冒泡:事件會從最內層的元素開始發生,一直向上傳播,知道觸發 document 物件。

因此在事件冒泡下,p 元素髮生 click 事件的順序為 p->div->body->html->document

事件繫結:

js 通過 addEventListener 繫結事件。addEventListener 的第三個引數就是為事件冒泡和事件捕獲準備的。

addEventListener 有三個引數:

element.addEventListener(event, function, useCapture)

  • 第一個引數是:需要繫結的事件
  • 第二個引數是:觸發事件後要執行的函式
  • 第三個引數是:預設值是 false ,表示在 事件冒泡階段 呼叫事件處理函式;如果設定為 true ,則表示在 事件捕獲階段 呼叫事件處理函式。

事件代理(事件委託)

對於事件代理來說,在事件捕獲或者事件冒泡階段處理並沒有明顯的優劣之分,但是由於事件冒泡的事件流模型被所有的主流瀏覽器相容,從相容性角度來說通常使用事件冒泡模型。

為什麼要使用事件代理?
比如100個(甚至更多)li 標籤繫結事件,如果一個一個繫結,不僅相當麻煩,還會佔用大量的記憶體空間,降低效能。而使用事件代理作用如下:

  • 程式碼簡潔
  • 減少瀏覽器記憶體佔用

事件代理的原理:

事件代理(事件委託)是利用事件的冒泡原理來實現的,比如當我們點選內容的li標籤時,會冒泡到外層的ul標籤上。因此,當我們想給很多個li標籤新增事件的時候,可以給他的父級元素新增對應的事件,當觸發任意li元素時,會冒泡到其父級元素,這時繫結在父級元素的事件就會被觸發,這就是事件代理(委託),委託他們的父級元素代為執行事件。

demo

<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
<script>
	window.onload = function(){
    	var oUl = document.getElementById("ul1");
   		oUl.onclick = function(){
        	alert(123);
    	}
	}
</script>

參考文章:https://segmentfault.com/a/1190000005654451