從一個事件綁定提及 - DOM

事件綁定的方式

給 DOM 元素綁定事件分爲兩大類:在 html 中直接綁定 和 在 JavaScript 中綁定。javascript

Bind in HTML

在 HTML 中綁定事件叫作內聯綁定事件,HTML 的元素中有如 onclick 這樣的 on*** 屬性,它能夠給這個 DOM 元素綁定一個類型的事件,主要是這樣的:html

<div onclick="***">CLICK ME</div>

這裏的 *** 有兩種形式:java

  • 用字符串表示一段函數:
<div onclick="var a = 1; console.log(a); console.log(this);">CLICK ME</div>

點擊能夠發現,console:瀏覽器

1
<div onclick=​"var a = 1;​ console.log(a)​;​ console.log(this)​;​">​CLICK ME​</div>​

var a = 1; console.log(a); console.log(this); 這一段字符串被看成 js 執行了,同時 this 指向當前這個點擊的元素。dom

  • 用函數名錶示:
<div onclick="foo(this)">CLICK ME</div>

這裏須要添加 script 去定義函數:函數

<script>
function foo(_this) {
console.log(_this);
console.log(this);
}
</script>

能夠觀察到,console:post

<div onclick=​"var a = 1;​ console.log(a)​;​ console.log(this)​;​">​CLICK ME​</div>​
window

這裏的 this 指向了全局,傳入的參數是點擊的 DOM 元素,其實和第一種方式是同樣的,都是在 onclick= 後面指向了一段js的字符串,不一樣的是在這個字符串中是執行了一個函數名,而這個函數咱們在全局中定義了,因此點擊的時候能夠執行,而後傳入的參數 this 也就是同樣的道理了。ui

或者多說一句,這裏的字符串纔是真正賦值給 onclick 的函數,這裏咱們是在函數裏面再執行了 foo 函數。this

然而這內聯的方式綁定時間不利於分離,因此通常咱們不推薦這種作法,因此也就再也不多闡述了spa

Bind in JavaScript

dom.onclick

先上栗子:

<div id="clickme">CLICK ME</div>
document.getElementById('clickme').onclick = function (e) {
console.log(this.id);
console.log(e);
};

觀察 console:

clickme
MouseEvent {isTrusted: true, screenX: 65, screenY: 87, clientX: 65, clientY: 13…}

首先,咱們獲取到了 dom 元素,而後給它的 onclick 屬性賦值了一個函數;

點擊 dom 咱們發現那個函數執行了,同時發現函數中的 this 是指向當前的這個 dom 元素;

細細一想,其實這和前面用的在 html 中 內聯 綁定函數是同樣的,咱們一樣是給 dom 的 onclick 屬性賦值一個函數,而後函數中的 this 指向當前元素,只是這個過程這裏咱們是在 js 中作的;

而還有一點區別就是前面的是賦值一段 js 字符串,這裏是賦值一個函數,因此能夠接受一個參數:event,這個參數是點擊的事件對象。

用賦值綁定函數的一個缺點就是它只能綁定一次:

document.getElementById('clickme').onclick = function (e) {
console.log(this.id);
};
document.getElementById('clickme').onclick = function (e) {
console.log(this.id);
};

能夠看到這裏只打印了一次 clickme

addEventListener

這個纔是咱們須要重點介紹的一個函數

語法

target.addEventListener(type, listener[, useCapture]);
  • target : 表示要監聽事件的目標對象,能夠是一個文檔上的元素 Document 自己,Window 或者 XMLHttpRequest
  • type : 表示事件類型的字符串,好比: clickchangetouchstart …;
  • listener : 當指定的事件類型發生時被通知到的一個對象。該參數必是實現 EventListener 接口的一個對象或函數。
  • useCapture : 設置事件的 捕獲或者冒泡 (後文闡述) ,true: 捕獲,false: 冒泡*,默認爲 false

簡單栗子

<div id="clickme">CLICK ME</div>
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1);

function foo1(event) {
console.log(this.id);
console.log(event);
}

觀察 console:

clickme
MouseEvent {isTrusted: true, screenX: 37, screenY: 88, clientX: 37, clientY: 14…}

監聽函數中的 this 指向當前的 dom 元素,函數接受一個 event 參數。

綁定函數

綁定多個函數

addEventListener 能夠給同一個 dom 元素綁定多個函數:

<div id="clickme">CLICK ME</div>
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1);
clickme.addEventListener('click', foo2);

function foo1(event) {
console.log(111);
}

function foo1(event) {
console.log(222);
}

能夠看到 console:

111
222

咱們能夠看到兩個函數都執行了,而且執行順序按照綁定的順序執行。

改變一下,若是咱們的 useCapture 參數不一樣呢?
看下面 3 組對比:

1
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1, true);
clickme.addEventListener('click', foo2, true);

function foo1(event) {
console.log(111);
}

function foo1(event) {
console.log(222);
}
2
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1, true);
clickme.addEventListener('click', foo2, false);

function foo1(event) {
console.log(111);
}

function foo1(event) {
console.log(222);
}
3
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1, false);
clickme.addEventListener('click', foo2, true);

function foo1(event) {
console.log(111);
}

function foo1(event) {
console.log(222);
}

能夠看到,console 並無改變,因此執行順序只和綁定順序有關,和 useCapture 無關。

結論:
咱們能夠給一個 dom 元素綁定多個函數,而且它的執行順序按照綁定的順序執行。

同一個元素綁定同一個函數

咱們給一個 dom 元素綁定同一個函數兩次試試:

<div id="clickme">CLICK ME</div>
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1);
clickme.addEventListener('click', foo1);

function foo1(event) {
console.log(111);
}

觀察 console:

111

 

能夠看到函數只執行了一次;

換一種方式:

var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1, true);
clickme.addEventListener('click', foo1, false);

function foo1(event) {
console.log(111);
}

觀察 console:

111
111

 

能夠看到函數執行了兩次。

結論:
咱們能夠給一個 dom 元素綁定同一個函數,最多隻能綁定 useCapture 類型不一樣的兩次。

IE下使用attachEvent/detachEvent

addEventListener 只支持到 IE 9,因此爲了兼容性考慮,在兼容 IE 8 以及如下瀏覽器能夠用 attachEvent 函數,和 addEventListener 函數表現同樣,除了它綁定函數的 this 會指向全局這個缺點之外

事件執行順序的 PK

1. addEventListener 和 dom.onclick

<div id="clickme">CLICK ME</div>
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1, true);
clickme.onclick = foo2;
clickme.addEventListener('click', foo3, true);

function foo1(event) {
console.log(111);
}

function foo2(event) {
console.log(222);
}

function foo3(event) {
console.log(333);
}

觀察 console:

111
222
333

可見執行順序只和綁定順序有關

2. addEventListener 間的比較

見上文

事件的解綁

與事件綁定相對應的就是事件解綁了。

1. 經過 dom 的 on** 屬性設置的事件

對於 Bind in HTML 和 dom.onclick 綁定的事件均可以用 dom.onclick = null 來解綁事件。

2. removeEventListener

經過 addEventListener 綁定的事件能夠使用 removeEventListener 來解綁, removeEventListener 接受的參數和 addEventListener 是同樣的

<div id="clickme">CLICK ME</div>
var clickme = document.getElementById('clickme');

clickme.addEventListener('click', foo1);

function foo1(event) {
console.log(111);
}
clickme.removeEventListener('click', foo1, true);

這裏發現事件並無取消綁定,發現 removeEventListener 的 useCapture 的參數原來和綁定時候傳入的不一致,咱們改爲 false 以後發現事件取消了。

結論:
對於使用 removeEventListener 函數解綁事件,須要傳入的 listener useCapture 應和 addEventListener 一致才能夠解綁事件。

3. detachEvent

與 attachEvent 對應

DOM 事件

DEMO

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div class="div1">
<div class="div2">
<div class="div3">

</div>
</div>
</div>
</body>
</html>

事件冒泡

事件開始時由最具體的元素接受,而後逐級向上傳播到較爲不具體的節點。

好比上面的 HTML ,冒泡的順序: div3 -> div3 -> div1 -> body -> html -> document (-> window)

事件捕獲

事件捕獲的思想是不太具體的DOM節點應該更早接收到事件,而最具體的節點應該最後接收到事件。與事件冒泡的順序相反。

好比上面的 HTML ,捕獲的順序: document -> html -> body -> div1 -> div2 -> div3

DOM事件流

DOM事件流包括三個階段:事件捕獲階段、處於目標階段、事件冒泡階段。首先發生的事件捕獲,爲截獲事件提供機會。而後是實際的目標接受事件。最後一個階段是時間冒泡階段,能夠在這個階段對事件作出響應。

回到 addEventListener

咱們來作幾個小對比:

<div id="wrap">
<div id="clickme">CLICK ME</div>
</div>

栗子 1


document.getElementById('wrap').addEventListener('click', foo1, false);
document.getElementById('clickme').addEventListener('click', foo2, false);

function foo1(event) {
console.log(111);
}
function foo2(event) {
console.log(222);
}

console:

222
111

這裏兩個事件都是冒泡類型,因此是從內到外;

栗子 2


document.getElementById('wrap').addEventListener('click', foo1, true);
document.getElementById('clickme').addEventListener('click', foo2, true);

function foo1(event) {
console.log(111);
}
function foo2(event) {
console.log(222);
}

console:

111
222

這裏兩個事件都是捕獲類型,因此是從外到內;

栗子 3


document.getElementById('wrap').addEventListener('click', foo1, false);
document.getElementById('clickme').addEventListener('click', foo2, true);

function foo1(event) {
console.log(111);
}
function foo2(event) {
console.log(222);
}

console:

222
111

wrap 事件是冒泡,clickme 事件是捕獲,根據 dom 的事件流,先執行了捕獲階段(這裏是目標階段)再到冒泡階段。

栗子 4


document.getElementById('wrap').addEventListener('click', foo1, true);
document.getElementById('clickme').addEventListener('click', foo2, false);

function foo1(event) {
console.log(111);
}
function foo2(event) {
console.log(222);
}

console:

111
222

clickme 事件是冒泡,wrap 事件是捕獲,根據 dom 的事件流,先執行了捕獲階段(這裏是目標階段)再到冒泡階段。

阻止 冒泡/捕獲

<div id="wrap">
<div id="clickme">CLICK ME</div>
</div>

document.getElementById('wrap').addEventListener('click', foo1);
document.getElementById('clickme').addEventListener('click', foo2);

function foo1(event) {
console.log(111);
}
function foo2(event) {
event.stopPropagation();
console.log(222);
}
相關文章
相關標籤/搜索