一、兼容瀏覽器的事件監聽器
主要是兼容IE8以前的瀏覽器,addEvent方法中介紹了三種綁定事件的方式,其中要注意:
(1)在IE的事件綁定方式中,事件處理函數裏面的事件對象時掛在window上面的,所以獲取event對象時需要利用window.event來獲取
(2)addEventListener添加監聽器時注意事件的冒泡和事件的捕獲,即該方法的第三個參數,事件的冒泡和捕獲注意的事項參見下文
(3)利用屬性綁定事件只能爲同一個類型的事件添加一個事件處理函數,其他的兩種方式均可以爲同一類型的事件添加多個處理函數。所以屬性綁定事件移除時直接賦值null即可。
EventUnit={
addEvent:function(ele,type,handler){
if(ele.addEventListener)
{
ele.addEventListener(type,handler,false);
}
else if(ele.attachEvent)
{
ele.attachEvent('on'+type,handler);
}
else ele['on'+type]=handler;
},
removeEvent:function (ele,type,handler) {
if(ele.removeEventListener)
ele.removeEventListener(type,handler);
else if(ele.dispatchEvent())
ele.dispatchEvent('on'+type,handler);
else ele['on'+type]=null;
},
stopPropagation:function (ev) {
if(ev.stopPropagation) ev.stopPropagation();
else ev.cancelBubble=true;
},
preventDefault:function (ev) {
if(ev.preventDefault)ev.preventDefault();
else ev.returnValue=false;
},
getTarget:function(ev)
{
return ev.target||ev.srcElement;
},
getEvent:function (ev) {
var ev=ev||window.event;
return ev;
}
}
二、事件的捕獲和冒泡
1、基本概念
(1)在IE8+的瀏覽器以及谷歌等瀏覽器中事件的發生均有三個階段:事件的捕獲–》事件觸發對象–》事件冒泡
(2)事件捕獲:
當event.target觸發事件時,事件首先會發生在捕獲階段,即從上到下執行階段,當該元素的祖先元素註冊了同類型的事件且事件時發生捕獲階段(addEventListener的第三個參數設置爲true,默認情況下所有事件時發生在冒泡階段的,除了不能發生冒泡的事件)時,會先觸發祖先元素的事件函數,一次向下執行。
(3)事件的冒泡則與事件的捕獲的發生過程想法,事件的執行過程時從event.target向上冒泡執行的,不能冒泡的事件有:mouseenter/mouseleave/focus/blur/load等
2、事件冒泡捕獲的具體情況
在同一個元素上註冊同一類型事件的兩種處理方式的回調函數,即既有冒泡的回調也有捕獲的回調
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
#one{
width:400px;
height:400px;
border:2px solid red;
}
#two{
width:300px;
height:300px;
border:2px solid blue;
}
#three{
width:200px;
height:200px;
border:2px solid chartreuse;
}
#four{
width:100px;
height:100px;
border:2px solid rosybrown;
}
</style>
</head>
<body>
<div id='one'>
<div id='two'>
<div id='three'>
<div id='four'>
</div>
</div>
</div>
</div>
<script type='text/javascript'>
var one=document.getElementById('one');
var two=document.getElementById('two');
var three=document.getElementById('three');
var four=document.getElementById('four');
one.addEventListener('click',function(){
alert('one,bubble');
},false);
one.addEventListener('click',function(){
alert('one,capture');
},true);
two.addEventListener('click',function(){
alert('two,bubble');
},false);
two.addEventListener('click',function(){
alert('two,capture');
},true);
three.addEventListener('click',function(){
alert('three,capture');
},true);
four.addEventListener('click',function(){
alert('four');
},true);
</script>
</body>
</html>
(1)點擊元素只註冊一種類型事件(捕獲或者冒泡)
如果點擊的是元素three,則執行的結果是:one,capture –> two,capture –> three,bubble–> two ,bubble–> one ,bubble
由此可以看出事件是嚴格遵循事件的處理的三個階段的
(2)當點擊的元素本身既具有捕獲和冒泡事件時(點擊two)
程序的執行結果是:one,capture –> two,bubble –> two,capture–>one,bubble
是不是看到這樣的執行結果你覺得很奇怪,爲什麼two,bubble會先於two,capture呢?原因是在當前點擊的元素上如果同時註冊有捕獲和冒泡事件其執行順序是按照註冊的先後順序,先註冊,先執行。所以纔出現了這樣的結果,你可以交換two事件的註冊順序後,繼續點擊two看看效果。當你點擊one時就會發現執行的順序也是按照註冊的順序執行的。
3、利用事件冒泡實現事件代理
同一類型的事件當在對個子元素上進行添加綁定時,會添加代碼的冗餘度,同時使得代碼的可讀性變差,因而可以利用時間的冒泡的方式將該事件綁定在其公共的父元素上,減少代碼,同時當動態添加子元素時,也可以爲該元素綁定上事件,如果是採用原始的方式,則動態添加的元素上將不會有時間處理函數。
在下面的代碼中可以做測試,放開註釋的部分既可以看到效果,如果要對比可以參見我的另一篇博客阿里前端內推筆試題
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--code here-->
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, minimal-ui">
<meta name="format-detection" content="telephone=no">
<title>demo</title>
<style>
* { padding: 0; margin: 0;}
.record-head{width:100%}
.head, li div { display: inline-block; width: 20%; text-align: center; }
li .id, li .sex, .id, .sex { width: 10%; }
li .name, .name { width: 10%; }
li .tel, .tel { width: 20%; }
.del,.user-delete { width:10%; }
ul { list-style: none; }
.user-delete { cursor: pointer; }
</style>
</head>
<body>
<div id="J_container">
<div class="record-head">
<div class="head id">序號</div>
<div class="head name">姓名</div>
<div class="head sex">性別</div>
<div class="head tel">電話號碼</div>
<div class="head province">省份</div>
<div class="head del">操作</div>
</div>
<ul id="J_List">
<li>
<div class="id">1</div>
<div class="name">張三</div>
<div class="sex">男</div>
<div class="tel">13788888888</div>
<div class="province">浙江</div>
<div class="user-delete">刪除</div>
</li>
<li>
<div class="id">2</div>
<div class="name">李四</div>
<div class="sex">女</div>
<div class="tel">13788887777</div>
<div class="province">四川</div>
<div class="user-delete">刪除</div>
</li>
<li>
<div class="id">3</div>
<div class="name">王二</div>
<div class="sex">男</div>
<div class="tel">13788889999</div>
<div class="province">廣東</div>
<div class="user-delete">刪除</div>
</li>
</ul>
</div>
<script>
class Concat{
constructor(){
this.init();
}
init(){
let Ul = document.querySelector("#J_List");
/*利用代理模式綁定事件,一則可以避免使用循環綁定,
二對於動態添加元素也可以觸發事件,
但是有個問題是點擊ol其他的位置可能會導致事件的響應,所以可以根據功能進行權衡,選擇哪一種方式*/
Ul.addEventListener("click",(e) => {
Ul.removeChild(e.target.parentNode);
},false)
}
}
new Concat();
/*測試用的,動態添加事件*/
// let li = document.createElement("li");
// li.innerHTML = ` <div class="id">3</div>
// <div class="name">三二</div>
// <div class="sex">男</div>
// <div class="tel">13788889999</div>
// <div class="province">廣東</div>
// <div class="user-delete">刪除</div>`;
// let ul = document.querySelector("#J_List");
// ul.appendChild(li);
</script>
</body>
</html>