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

閒淡超人發表於2018-06-20

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

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

js事件的捕獲和冒泡圖

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事件的原始碼

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的原始碼

    jQuery->return false 原始碼

  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中的事件機制(帶例項)

事件模型

相關文章