文章目錄
- 事件
- 事件綁定
- 事件流
- DOM标準事件流
- 阻止事件冒泡/捕獲
- 阻止預設事件
- 事件代理(委托)
事件
JavaScript 使我們有能力建立動态頁面。事件是可以被 JavaScript 偵測到的行為。
網頁中的每個元素都可以産生某些可以觸發 JavaScript 函數的事件。
比方說,我們可以在使用者點選某按鈕時産生一個 onClick 事件來觸發某個函數。事件在 HTML 頁面中定義。
事件舉例:
- 滑鼠點選
- 頁面或圖像載入
- 滑鼠懸浮于頁面的某個熱點之上
- 在表單中選取輸入框
- 确認表單
- 鍵盤按鍵
事件綁定
事件綁定的三種方式:
<!-- 1. html标簽事件綁定 -->
<button onclick="show()" id="btn">Show</button>
<script>const btn = document.getElementById("btn");
// 2. js事件綁定
btn.onclick = show;
// 3. 事件監聽
btn.addEventListener("click", show);
function show(event) {
console.log(event.target); // 2 3方式有event傳回
event.preventDefault(); // 阻止預設行為
console.log('show');
}</script>
事件流
事件流描述的是從頁面中接收事件的順序。
事件發生時會在元素節點與根節點之間按照特定的順序傳播,路徑所經過的所有節點都會收到該事件,這個傳播過程即DOM事件流。
事件傳播的順序對應浏覽器的兩種事件流模型:
- 冒泡型事件流:從 DOM 樹的葉子到根。(預設)
- 捕獲型事件流:從 DOM 樹的根到葉子。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="div">Click me!</div>
<script>.getElementById("div").addEventListener("click", (event) => {
console.log('this is div');
});
document.body.addEventListener("click", (event) => {
console.log('this is body');
});
document.documentElement.addEventListener("click", (event) => {
console.log('this is html');
});
document.addEventListener("click", (event) => {
console.log('this is document');
});
// 預設是事件捕獲,是以按順序輸出:
// this is div
// this is body
// this is html
// this is document</script>
</body>
</html>
上面這段html代碼中,單擊了頁面中的
<div>
元素,
在冒泡型事件流中click事件傳播順序為
<div> => <body> => <html> => document
(預設)
在捕獲型事件流中click事件傳播順序為
document => <html> => <body> => <div>
note:
-
所有現代浏覽器都支援事件冒泡,但在具體實作中略有差别:
IE5.5及更早版本中事件冒泡會跳過元素(從body直接跳到document)。
IE9、Firefox、Chrome、和Safari則将事件一直冒泡到window對象。
- IE9、Firefox、Chrome、Opera、和Safari都支援事件捕獲。盡管DOM标準要求事件應該從document對象開始傳播,但這些浏覽器都是從window對象開始捕獲事件的。
- 由于老版本浏覽器不支援,很少有人使用事件捕獲。建議使用事件冒泡。
DOM标準事件流
DOM标準采用捕獲+冒泡。兩種事件流都會觸發DOM的所有對象,從document對象開始,也在document對象結束。
DOM标準規定事件流包括三個階段:
- 事件捕獲階段:實際目标
在捕獲階段不會接收事件。也就是在捕獲階段,事件從<div>
到document
再到<html>
就停止了。<body>
- 處于目标階段:事件在
上發生并處理。但是事件處理會被看成是冒泡階段的一部分。<div>
- 事件冒泡階段:事件又傳播回文檔。
綁定事件時通過addEventListener函數,它有三個參數,第三個參數若是true,則表示采用事件捕獲,若是false(預設),則表示采用事件冒泡。
<div id="box1">box1
<div id="box2">box2
<div id="box3">box3</div>
</div>
</div>
<script>.addEventListener('click', function () {
console.log('box1 捕獲階段');
}, true);
box2.addEventListener('click', function () {
console.log('box2 捕獲階段');
}, true);
box3.addEventListener('click', function () {
console.log('box3 捕獲階段');
}, true);
box1.addEventListener('click', function () {
console.log('box1 冒泡階段');
}, false);
box2.addEventListener('click', function () {
console.log('box2 冒泡階段');
}, false);
box3.addEventListener('click', function () {
console.log('box3 冒泡階段');
}, false);</script>
element.addEventListener(event, function, useCapture)
第三個參數
useCapture
,可選。布爾值,指定事件是否在捕獲或冒泡階段執行:
-
- 事件句柄在捕獲階段執行true
-
(預設)- 事件句柄在冒泡階段執行false
阻止事件冒泡/捕獲
使用
event.stopPropagation()
起到阻止捕獲和冒泡階段中目前事件的進一步傳播。
- W3C的方法是:
event.stopPropagation()
- IE則是使用:
event.cancelBubble = true
p.addEventListener("click", (event) => {
event.stopPropagation(); // 阻止事件冒泡
console.log('this is p'); // 隻會輸出 'this is p'
});
document.addEventListener("click", (event) => {
event.stopPropagation(); // 阻止事件捕獲
console.log('this is document'); // 隻會輸出 'this is document'
}, true);
相容IE的寫法:
window.event? window.event.cancelBubble = true : event.stopPropagation();
阻止預設事件
- W3C的方法是:
event.preventDefault()
- IE則是使用:
event.returnValue = false
既然是說預設行為,當然是元素必須有預設行為才能被取消,如果元素本身就沒有預設行為,調用當然就無效了。
<a href="https://www.baidu.com/" id="a">阻止預設跳轉</a>
<script>.getElementById("a").addEventListener("click", (event) => {
event.preventDefault();
console.log('已阻止a連結跳轉');
});</script>
事件代理(委托)
事件代理的原理用到的就是事件冒泡和目标元素,把事件處理器添加到父元素,等待子元素事件冒泡,并且父元素能夠通過target(IE為srcElement)判斷是哪個子元素,進而做相應處理。
<body>
<ul id="color-list">
<li>red</li>
<li>orange</li>
<li>yellow</li>
<li>green</li>
<li>blue</li>
<li>indigo</li>
<li>purple</li>
</ul>
<script>// 不使用事件代理
(function(){
var colorList = document.getElementById("color-list");
var colors = colorList.getElementsByTagName("li");
for (var i = 0; i < colors.length; i++) {
colors[i].addEventListener('click', showColor);
};
function showColor(e) {
e = e || window.event;
var targetElement = e.target || e.srcElement;
console.log(targetElement.innerHTML);
}
})();
// 使用事件代理
(function(){
var colorList = document.getElementById("color-list");
colorList.addEventListener('click', showColor);
function showColor(e) {
e = e || window.event;
var targetElement = e.target || e.srcElement;
console.log(targetElement.innerHTML);
}
})();</script>
</body>
事件代理的好處:
- 将多個事件處理器減少到一個,因為事件處理器要駐留記憶體,這樣就提高了性能。想象如果有一個100行的表格,對比傳統的為每個單元格綁定事件處理器的方式和事件代理(即table上添加一個事件處理器),不難得出結論,事件代理确實避免了一些潛在的風險,提高了性能。
- DOM更新無需重新綁定事件處理器,因為事件代理對不同子元素可采用不同處理方法。如果新增其他子元素(a,span,div等),直接修改事件代理的事件處理函數即可,不需要重新綁定處理器,不需要再次循環周遊。
<li><span>li中的span的内容</span></li>
<script>$(document).on('click', 'li', function(e){
alert('li');
});
$(document).on('click', 'span', function(e){
alert('span');
})</script>
$(document).on('click', 'li', function(e){
alert('li');
});
$(document).on('click', 'span', function (e) {
alert('span');
e.stopPropagation(); // 阻止了事件冒泡,但不會阻擊預設行為(如超連結的跳轉)
// return false; // 阻止了事件冒泡,也阻止了預設行為
})