在上一篇《JS知識點大雜燴》中說到了事件流但沒有詳細的介紹,這篇文章就來介紹一下事件流。bash
事件流一共由三個階段分別是:閉包
1.捕獲階段
2.目標階段
3.冒泡階段
複製代碼
事件綁定你們都知道,有DOM0級(on+type)和DOM2級(addEventListener),我以爲說那麼多概念很差理解,直接看代碼吧,爲了方便我就直接使用id來獲取元素。ui
<div id="box1"></div>
box1.onclick = function(){
console.log('box1');
}
複製代碼
輸出了box1
這個咱們都知道,再來看一下。this
<div id="box1"></div>
box1.onclick = function(){
console.log('box1');
}
box1.onclick = function(){
console.log('box1 two');
}
複製代碼
輸出了box1 two
,由於DOM0級會覆蓋掉以前在同一元素上面的綁定,再來看一下。spa
<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');
}
複製代碼
<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冒泡)。 咱們再來看看捕獲階段是怎麼樣的3d
<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看到 代理
那冒泡跟捕獲的執行順序是什麼樣的呢?我分別在每個元素上綁定了兩個階段的同一事件,咱們來看看觸發的順序。code
<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看到,先捕獲後冒泡。 cdn
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上面不支持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 哈哈,開玩笑啊。
咱們先來講一下不支持冒泡的事件: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)
複製代碼
box1.attachEvent('onclick',function () {
console.log('attachEvent', this);
})
複製代碼
attachEvent [object Window]
咱們發現,IE六、七、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);
}
複製代碼
這就是事件委託。