在上一篇《JS知識點大雜燴》中說到了事件流但沒有詳細的介紹,這篇文章就來介紹一下事件流。
事件流一共由三個階段分別是:
1.捕獲階段
2.目標階段
3.冒泡階段
複製程式碼
事件繫結大家都知道,有DOM0級(on+type)和DOM2級(addEventListener),我覺得說那麼多概念不好理解,直接看程式碼吧,為了方便我就直接使用id來獲取元素。
- DOM0級
<div id="box1"></div>
box1.onclick = function(){
console.log('box1');
}
複製程式碼
輸出了box1
這個我們都知道,再來看一下。
<div id="box1"></div>
box1.onclick = function(){
console.log('box1');
}
box1.onclick = function(){
console.log('box1 two');
}
複製程式碼
輸出了box1 two
,因為DOM0級會覆蓋掉之前在同一元素上面的繫結,再來看一下。
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
box1.onclick = function(){
console.log('box1');
}
box2.onclick = function(){
console.log('box2');
}
box3.onclick = function(){
console.log('box3');
}
複製程式碼
當我們點選box1時都知道輸出box1,可是當我們點選box3時彈出什麼呢?
你可能會感覺奇怪,為什麼我點選的box3怎麼其他的也會觸發?因為事件冒泡。那什麼是事件冒泡呢?概念請自行百度,直接上圖。
這就叫做事件冒泡,一級一級往上冒直到window這裡我沒有畫出來。DOM0級只支援冒泡階段。
- DOM2級
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
box1.addEventListener('click', function(){
console.log('box1');
},false);
box2.addEventListener('click', function(){
console.log('box2');
},false);
box3.addEventListener('click', function(){
console.log('box3');
},false);
複製程式碼
輸出跟上面是一樣的,因為我們繫結在了冒泡階段。(true捕獲,false冒泡)。 我們再來看看捕獲階段是怎麼樣的
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
box1.addEventListener('click', function(){
console.log('box1');
},true);
box2.addEventListener('click', function(){
console.log('box2');
},true);
box3.addEventListener('click', function(){
console.log('box3');
},true);
複製程式碼
我們點選box3看到
你可能會發現順序反過來了,那這是為什麼呢?因為事件捕獲,那什麼是時間捕獲呢?概念請自行百度,直接上圖。 這就是捕獲階段,跟冒泡階段完全相反。那冒泡跟捕獲的執行順序是什麼樣的呢?我分別在每一個元素上繫結了兩個階段的同一事件,我們來看看觸發的順序。
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
box1.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);
複製程式碼
我們點選box3看到,先捕獲後冒泡。
那是不是都這樣呢?我們稍微改動一下。box1.addEventListener('click', function(){
console.log('box1 捕獲階段');
},true);
box2.addEventListener('click', function(){
console.log('box2 捕獲階段');
},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);
box3.addEventListener('click', function(){
console.log('box3 捕獲階段');
},true); // 將box3的捕獲階段放到box3的冒泡階段後面
複製程式碼
看看觸發的順序是不是還一樣呢?
發現反過來了,其實這就叫做目標階段吧。在你觸發事件的目標元素身上不區分冒泡捕獲,按繫結的順序來執行。我們用圖來看一下。
這樣是不是太簡單我們來一點複雜的。<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
box1.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);
box1.onclick = function () {
console.log('box1 51561');
}
box2.onclick = function () {
console.log('box2');
}
box3.onclick = function () {
console.log('box3');
}
box1.onclick = function () {
console.log('box1');
}
複製程式碼
觸發順序是什麼樣的?(我覺得你最好先自己把答案寫出來)
看看你答對了沒有
這樣會不會太簡單,換一下順序
box1.onclick = function () {
console.log('box1 51561');
}
box2.onclick = function () {
console.log('box2');
}
box3.onclick = function () {
console.log('box3');
}
box1.onclick = function () {
console.log('box1');
}
box1.addEventListener('click', function(){
console.log('box1 捕獲階段');
},true);
box2.addEventListener('click', function(){
console.log('box2 捕獲階段');
},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);
box3.addEventListener('click', function(){
console.log('box3 捕獲階段');
},true);
複製程式碼
答對了嗎?
- 再說一下冒泡和捕獲
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
box1.onclick = function () {
console.log('box1 51561');
}
box2.onclick = function () {
console.log('box2');
}
box3.onclick = function () {
console.log('box3');
}
box1.onclick = function () {
console.log('box1');
}
box1.addEventListener('click', function(){
console.log('box1 捕獲階段');
},true);
box2.addEventListener('click', function(){
console.log('box2 捕獲階段');
},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);
box3.addEventListener('click', function(){
console.log('box3 捕獲階段');
},true);
複製程式碼
你說這樣點選會輸出什麼?你是不是猶豫了?說明你還是不懂冒泡和捕獲。
不要讓你看到的騙了你,冒泡是DOM結構的父子關係而不是看起來是不是包裹的關係。(答案同上面)。
- 忘了IE了,說一下IE的事件機制。
IE上面不支援addEventListener但是它有attachEvent
box1.onclick = function () {
console.log('box1 51561');
}
box2.onclick = function () {
console.log('box2');
}
box3.onclick = function () {
console.log('box3');
}
box1.onclick = function () {
console.log('box1');
}
box1.attachEvent('onclick', function (){
console.log('box1 attachEvent')
})
box2.attachEvent('onclick', function (){
console.log('box2 attachEvent')
})
box3.attachEvent('onclick', function (){
console.log('box3 attachEvent')
})
複製程式碼
box1.attachEvent('onclick', function (){
console.log('box1')
})
box1.attachEvent('onclick', function (){
console.log('box2')
})
box1.attachEvent('onclick', function (){
console.log('box3')
})
複製程式碼
這個會輸出什麼?(提示:不會覆蓋) 答案是:box1 box2 box3 哈哈,開玩笑啊。
是不是很奇怪,IE中該事件是先繫結的後輸出。IE6、7、8,不支援事件捕獲只支援事件冒泡。- 阻止事件冒泡相容
我們先來說一下不支援冒泡的事件:blur、focus、mouseenter、mouseleave。(我就知道這些) 還是這個例子,我們看一下阻止冒泡。
box1.onclick = function (){
console.log('box1')
}
box2.onclick = function (){
console.log('box2')
}
box3.onclick = function (e){
e.stopPropagation();
console.log('box3')
}
複製程式碼
只輸出了box3. 雖然阻止了冒泡但在IE8及以下是不好使的,我們看一下相容的寫法。
function stopPropagate(e){
var event = e || window.event;
if(event.stopPropagation){
event.stopPropagation();
}else if(event.cancelBubble){ //IE
event.cancelBubble = true;
}
}
複製程式碼
阻止預設事件相容
function preventDef(e){
var g = e || window.event;
if(g.preventDefault){
g.preventDefault();
}else if(g.returnValue){
g.returnValue = false;
}
return false;
}
複製程式碼
我們再來看看事件繫結的this指向
box1.onclick = function (){
console.log('onclick', this);
}
box1.addEventListener('click',function () {
console.log('addEventListener', this);
}, false)
複製程式碼
IE6、7、8,事件繫結的this指向
box1.attachEvent('onclick',function () {
console.log('attachEvent', this);
})
複製程式碼
attachEvent [object Window]
我們發現,IE6、7、8 this指向window
- 擴充套件 事件委託
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
複製程式碼
如果我們要監聽每一個li的行為,你會不會這麼做。
let li = document.getElementsByTagName("li");
for (var i = 0; i < li.length; i++) {
li.onclick = ()=>{
console.log(i);
}
}
複製程式碼
評論區有位大佬指出了我的錯誤,因為這樣形成了閉包,得不到輸出的結果。因為當時只想演示事件委託沒有注意到這個情況,在此為讀者們表示深深的歉意。下面是更改之後的,之所以不把錯誤的修改是想告誡自己還有和我犯同樣錯誤的人。
let li = document.getElementsByTagName("li");
//ES6
for (let i = 0; i < li.length; i++) {
li[i].onclick = ()=>{
console.log(i);
}
}
//IIFE(1)
for (var i = 0; i < li.length; i++) {
(function(j){
li[j].onclick = ()=>{
console.log(j);
}})(i)
}
//IIFE(2)
for (var i = 0; i < li.length; i++) {
li[i].onclick = (function(j){
return ()=>{
console.log(j);
}
})(i);
}
複製程式碼
這樣做是對的單不夠好,要是再加幾個li或有很多的100|1000個li你還這樣做是不是感覺就不好了。我們就需要為每一個li註冊事件,麻煩不說,註冊很多事件就不好。那麼我們怎麼辦的,使用事件委託,就是把你的事件委託給別人(父級),利用事件冒泡,只指定一個事件處理程式,就可以管理某一型別的所有事件。
我們來看一下。
ul.onclick = function (e){
console.log(e.target);
}
複製程式碼
這就是事件委託。
- 優點:
- 可以大量節省記憶體佔用,減少事件註冊。
- 可以實現當新增子物件時,無需再對其進行事件繫結,對於動態內容部分尤為合適
- 缺點:
- 事件代理的常用應用應該僅限於上述需求,如果把所有事件都用事件代理,可能會出現事件誤判。即本不該被觸發的事件被繫結上了事件。