JavaScript事件機制——記一次認真準備的技術分享

原文來自我的掘金:https://juejin.im/post/5b29cdaa518825749d2d557a

先問幾個問題,你是否能快速閃過答案?

  1. 自下而上(冒泡)事件怎麼寫,自上而下(捕獲)又是怎麼寫?
  2. 捕獲型和冒泡型同時存在,誰生效?
  3. jquery的on或bind是冒泡,還是捕獲?
  4. 冒泡能夠阻止,那捕獲能夠阻止嗎?
  5. stopPropagation 和 stopImmediatePropagation的區別
  6. Event.bubbles,Event.eventPhase
  7. Event.cancelable,Event.cancelBubble,event.defaultPrevented
  8. 常用技巧

js事件的捕獲和冒泡圖

舉個例子:

點擊s2,s1分別會打印什麼?

div id="s1">s1
 <div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){ 
  console.log("s1 冒泡事件"); },false);
s2.addEventListener("click",function(e){ 
  console.log("s2 冒泡事件");},false);
s1.addEventListener("click",function(e){ 
  console.log("s1 捕獲事件");},true);
s2.addEventListener("click",function(e){
  console.log("s2 捕獲事件");},true);
</script>


//s1 捕獲事件
//s2 冒泡事件
//s2 捕獲事件
//s1 冒泡事件

點擊s2,click事件從document->html->body->s1->s2(捕獲前進)這裏在s1上發現了捕獲註冊事件,則輸出"s1 捕獲事件"到達s2,已經到達目的節點。

s2上註冊了冒泡和捕獲事件,先註冊的冒泡後註冊的捕獲,則先執行冒泡,輸出"s2 冒泡事件"

再在s2上執行後註冊的事件,即捕獲事件,輸出"s2 捕獲事件"

下面進入冒泡階段,按照s2->s1->body->html->documen(冒泡前進)在s1上發現了冒泡事件,則輸出"s1 冒泡事件"

jQuery的on事件是冒泡

下面貼上jquery的on事件的源碼

常用技巧

onclick -->事件冒泡,重寫onlick會覆蓋之前屬性,沒有兼容性問題

ele.onclik = null;   //解綁單擊事件,將onlick屬性設爲null即可

阻止默認事件(href=""鏈接,submit表單提交等)

  1. return false; 阻止獨享屬性(通過on這種方式)綁定的事件的默認事件

    ele.onclick = function() {
        ……                         //你的代碼
        return false;              //通過返回false值阻止默認事件行爲
    }
    
但是,在jQuery中,我們常用return false來阻止瀏覽器的默認行爲,那”return false“到底做了什麼?

當你每次調用”return false“的時候,它實際上做了3件事情:

  • event.preventDefault();

  • event.stopPropagation();

  • 停止回調函數執行並立即返回。

這3件事中用來阻止瀏覽器繼續執行默認行爲的只有preventDefault,除非你想要停止事件冒泡,否則使用return false會爲你的代碼埋下很大的隱患。

下面貼上jQuery的源碼

        2. event.preventDefault( ); 阻止通過 addEventListener( ) 添加的事件的默認事件

element.addEventListener(“click”, function(e){
    var event = e || window.event;
    ……
    event.preventDefault( );      //阻止默認事件
},false);

        3. event.returnValue = false; 阻止通過 attachEvent( ) 添加的事件的默認事件(此事件爲ie瀏覽器特有

element.attachEvent(“onclick”, function(e){
    var event = e || window.event;
    ……
    event.returnValue = false;  //阻止默認事件
},false);
把事件綁定以及事件解綁封裝成爲一個函數,兼容瀏覽器,包括IE6及以上(雖然現在基本上都放棄了IE9以下了hhhh)
// 事件綁定
function addEvent(element, eType, handle, bol) {
    if(element.addEventListener){           //如果支持addEventListener
        element.addEventListener(eType, handle, bol);
    }else if(element.attachEvent){          //如果支持attachEvent
        element.attachEvent("on"+eType, handle);
    }else{                                  //否則使用兼容的onclick綁定
        element["on"+eType] = handle;
    }
}

// 事件解綁
function removeEvent(element, eType, handle, bol) {
    if(element.addEventListener){
        element.removeEventListener(eType, handle, bol);
    }else if(element.attachEvent){
        element.detachEvent("on"+eType, handle);
    }else{
        element["on"+eType] = null;
    }
}

事件停止傳播 stopPropagation 和 stopImmediatePropagation

// 事件傳播到 element 元素後,就不再向下傳播了
element.addEventListener('click', function (event) {
  event.stopPropagation();
}, true);

// 事件冒泡到 element 元素後,就不再向上冒泡了
element.addEventListener('click', function (event) {
  event.stopPropagation();
}, false);

但是,stopPropagation方法只會阻止【該元素的當前事件(冒泡或者捕獲)】的傳播,不會阻止該節點的其他click事件的監聽函數。也就是說,不是徹底取消click事件,它還可以正常創建一個新的click事件。

element.addEventListener('click', function (event) {
  event.stopPropagation();
  console.log(1);
});

element.addEventListener('click', function(event) {
  // 會觸發
  console.log(2);
});

如果想要徹底阻止這個事件的傳播,不再觸發後面所有click的監聽函數,可以使用stopImmediatePropagation方法。注意:是針對該事件,比如你在click裏寫了這個方法,那【使用該方法之後】的該元素上綁定的方法將失效,但是別的mousedown,mouseover方法等還是生效的。親測過~

element.addEventListener('click', function (event) {
  // 會觸發
  console.log(‘改方法內的可以執行’);
  event.stopImmediatePropagation();
  // 會觸發
  console.log(1);
});

element.addEventListener('click', function(event) {
  // 不會被觸發
  console.log(2);
});

// Jquery同理
$(element).click(function() {
  // 不會觸發
  console.log(‘jquery click’)
})

$(element).hover(function() {
  // 會觸發
  console.log(‘jquery click’)
})


Event.bubbles,Event.eventPhase


Event.bubbles屬性返回一個布爾值,表示當前事件是否會冒泡。該屬性爲只讀屬性,一般用來了解 Event 實例是否可以冒泡。前面說過,除非顯式聲明,Event構造函數生成的事件,默認是不冒泡的。可以根據下面的代碼來判斷事件是否冒泡,從而執行不同的函數。

function goInput(e) {
  if (!e.bubbles) {
    passItOn(e);
  } else {
    doOutput(e);
  }
}

專門查了一下不支持冒泡的事件有:

  • UI事件(load, unload, scroll, resize)
  • 焦點事件(blur, focus)
  • 鼠標事件(mouseleave, mouseenter)

Event.eventPhase屬性返回一個整數常量,表示事件目前所處的階段。該屬性只讀。

var phase = event.eventPhase;

Event.eventPhase的返回值有四種可能。

  • 0,事件目前沒有發生。
  • 1,事件目前處於捕獲階段,即處於從祖先節點向目標節點的傳播過程中。
  • 2,事件到達目標節點,即Event.target屬性指向的那個節點。
  • 3,事件處於冒泡階段,即處於從目標節點向祖先節點的反向傳播過程中。


Event.cancelable,Event.cancelBubble,event.defaultPrevented


Event.cancelable屬性返回一個布爾值,表示事件是否可以取消。該屬性爲只讀屬性,一般用來了解 Event 實例的特性。

大多數瀏覽器的原生事件是可以取消的。比如,取消click事件,點擊鏈接將無效。但是除非顯式聲明,Event構造函數生成的事件,默認是不可以取消的。

var evt = new Event('foo');
evt.cancelable  // false
當Event.cancelable屬性爲true時,調用Event.preventDefault()就可以取消這個事件,阻止瀏覽器對該事件的默認行爲。注意,該方法只是取消事件對當前元素的默認影響,不會阻止事件的傳播。如果要阻止傳播,可以使用stopPropagation()或stopImmediatePropagation()方法。
function preventEvent(event) {
  if (event.cancelable) {
    event.preventDefault();
  } else {
    console.warn('This event couldn\'t be canceled.');
    console.dir(event);
  }
}

Event.cancelBubble屬性是一個布爾值,該屬性可以自行設置。如果設爲true,相當於執行Event.stopPropagation(),可以阻止事件的傳播。

注意:MDN裏說該特性已經從 Web 標準中刪除,雖然一些瀏覽器目前仍然支持它,但也許會在未來的某個時間停止支持,請儘量不要使用該特性。請使用 event.stopPropagation() 方法來代替該不標準的屬性.cancelBubble-MDN

Event.defaultPrevented屬性返回一個布爾值,表示該事件是否調用過Event.preventDefault方法。該屬性只讀。

if (event.defaultPrevented) {
  console.log('該事件已經取消了');
}

主要參考文檔:

詳解JS中的事件機制(帶實例)

事件模型


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章