麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > JavaScript > 正文

jQuery 1.9.1源碼分析系列(十)事件系統之主動觸發事件和模擬冒泡處理

2019-11-20 11:10:45
字體:
來源:轉載
供稿:網友

發現一個小點,先前沒有注意的

   stopPropagation: function() {      var e = this.originalEvent;      ...      if ( e.stopPropagation ) {        e.stopPropagation();      }

  jQuery重載stopPropagation函數調用的本地事件對象的stopPropagation函數阻止冒泡。也就是說,阻止冒泡的是當前節點,而不是事件源。

  說到觸發事件,我們第一反應是使用$(...).click()這種方式觸發click事件。這種方式毫無疑問簡潔明了,如果能使用這種方式推薦使用這種方式。但是如果是自定義事件呢?比如定義一個$(document).on("chuaClick","#middle",fn);這種情況怎么觸發事件?這就要用到$("#middle").trigger("chuaClick")了。

a.觸發事件低級API――jQuery.event.trigger

  trigger函數對所有類型事件的觸發提供了支持。這些事件主要分為兩類:普通瀏覽器事件(包含帶有命名空間的事件如"click.chua")、自定義事件。因為要統一處理,所以函數內部實現沒有調用.click()這種方式來對普通瀏覽器事件做捷徑處理,而是統一流程。處理過程如下

  1.獲取要觸發的事件(傳入的event可能是事件類型而不是事件對象)

event = event[ jQuery.expando ] ? event :new jQuery.Event( type, typeof event === "object" && event );

  2.修正瀏覽器事件(主要有修正事件源)和組合正確的事件處理參數data

  if ( type.indexOf(".") >= 0 ) {        //有命名空間的事件觸發; 先取出事件處理入口函數handle()使用的事件類型type        namespaces = type.split(".");        type = namespaces.shift();        namespaces.sort();      }      ...// 調用者可以傳遞jQuery.Event對象,普通對象,甚至是字符串      event = event[ jQuery.expando ] ?      event :      new jQuery.Event( type, typeof event === "object" && event );      event.isTrigger = true;      event.namespace = namespaces.join(".");      event.namespace_re = event.namespace ?      new RegExp( "(^|//.)" + namespaces.join("http://.(?:.*//.|)") + "(//.|$)" ) :      null;      // 重置result屬性,避免上次的結果殘留      event.result = undefined;      if ( !event.target ) {        event.target = elem;      }      // 克隆傳參data并將event放在傳參data的前面,創建出事件處理入口函數的參數列表,創建后結果可能是[event,data]      data = data == null ?      [ event ] :      jQuery.makeArray( data, [ event ] );

  后面這段組合事件處理參數列表data在后面處理時調用

  if ( handle ) {          handle.apply( cur, data );        }

  3.判斷是否是特殊節點對象的的特殊事件,是的話特殊處理

 special = jQuery.event.special[ type ] || {};  if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {    return;  }

  這里面需要特殊處理的事件比較少,這里列一下

 special: {      click.trigger: function(){   // checkbox, 觸發本地事件確保狀態正確if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {            this.click();            return false;          }      },      focus.trigger: function() {  // 觸發本地事件保證失焦/聚焦序列正確if ( this !== document.activeElement && this.focus ) {            try {              this.focus();              return false;            } catch ( e ) {              // Support: IE<9              // If we error on focus to hidden element (#1486, #12518),              // let .trigger() run the handlers            }          }      },      blur.trigger: function() {if ( this === document.activeElement && this.blur ) {            this.blur();            return false;          }      }    }

  4.從事件源開始遍歷父節點直到Window對象,將經過的節點保存(保存到eventPath)下來備用

for ( ; cur; cur = cur.parentNode ) {  eventPath.push( cur );  tmp = cur;}// 將window也壓入eventPath(e.g., 不是普通對象也不是斷開連接的DOM)if ( tmp === (elem.ownerDocument || document) ) {  eventPath.push( tmp.defaultView || tmp.parentWindow || window );}

  5.循環先前保存的節點,訪問節點緩存,如果有對應的事件類型處理隊列則取出其綁定的事件(入口函數)進行調用。      

// jQuery綁定函數處理:判斷節點緩存中是否保存相應的事件處理函數,如果有則執行       handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );       if ( handle ) {           handle.apply( cur, data );       }       // 本地綁定處理       handle = ontype && cur[ ontype ];       if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {           event.preventDefault();       }

  6. 最后處理瀏覽器默認事件,比如submit標簽的提交表單處理。

  // 如果沒有人阻止默認的處理,執行之      if ( !onlyHandlers && !event.isDefaultPrevented() ) {        ...      }

  注意:普通事件加上命名空間仍然屬于普通事件,普通調用方式依然其作用。比如$(document).on('click.chua',"#id",fn1).on("click","#id",fn2);當點擊“#id”節點的時候fn1依然會被調用。觸發指定命名空間事件的唯一方式是trigger:$("#id").trigger("click.chua"),此時只會調用fn1。

  從第4、5個步驟可以看到trigger的另外一個巨大作用――模擬冒泡處理。后面會分析到

b. 事件特殊處理jQuery.event.special(主要有事件替代、模擬冒泡)詳解

  委托設計是基于事件可冒泡的。但是有些事件是不可冒泡的,有的事件在不同的瀏覽器上支持的冒泡情況不同。還有不同的瀏覽器支持的事件類型也不盡相同。這些處理主要都被放在jQuery.event.special中。jQuery.event.special對象中保存著為適配特定事件所需的變量和方法。

  具體有:

delegateType / bindType (用于事件類型的調整)
setup (在某一種事件第一次綁定時調用)
add (在事件綁定時調用)
remove (在解除事件綁定時調用)
teardown (在所有事件綁定都被解除時調用)
trigger (在內部trigger事件的時候調用)
noBubble
_default
handle (在實際觸發事件時調用)
preDispatch (在實際觸發事件前調用)
postDispatch (在實際觸發事件后調用)

  看一下模擬冒泡的函數simulate

simulate: function( type, elem, event, bubble ) {      // 構建一個新的事件以區別先前綁定的事件.      // 新構建的事件避免阻止冒泡, 但如果模擬事件可以阻止默認操作的話,我們做同樣的阻止默認操作。      var e = jQuery.extend(        new jQuery.Event(),        event,        { type: type,          isSimulated: true,          originalEvent: {}        }        );      if ( bubble ) {        jQuery.event.trigger( e, null, elem );      } else {        jQuery.event.dispatch.call( elem, e );      }      if ( e.isDefaultPrevented() ) {        event.preventDefault();      }    }

  看到沒有,真正模擬冒泡函數是jQuery.event.trigger函數

special第一組

  這里面涉及到冒泡處理的問題。

special: {  load: {    //阻止觸發image.load事件冒泡到window.load    noBubble: true  },  click: {    //checkbox觸發時保證狀態正確    trigger: function() {if (...) {this.click();return false;}}  },  focus: {    //觸發本當前節點blur/focus事件 確保隊列正確    trigger: function() {      if ( this !== document.activeElement && this.focus ) {        try {          this.focus();          return false;        } catch ( e ) {          // IE<9,如果我們錯誤的讓隱藏的節點獲取焦點(#1486, #12518),          // 讓.trigger()運行處理器        }      }    },    delegateType: "focusin"  },  blur: {    trigger: function() {      if ( this === document.activeElement && this.blur ) {        this.blur();        return false;      }    },    delegateType: "focusout"  },  beforeunload: {    postDispatch: function( event ) {      //即使的returnValue等于undefined,Firefox仍然會顯示警告       if ( event.result !== undefined ) {        event.originalEvent.returnValue = event.result;      }    }  }}

  focus/blur本來是不冒泡的,但是我們依然可以通過$(document).on('focus ','#left',fn)來綁定,是怎么做到的?我們來看jQuery的處理

  第一步,將focus綁定的事件轉化為focusin來綁定,focusin在W3C的標準中是冒泡的,除開火狐之外的瀏覽器也確實支持冒泡(火狐瀏覽器focusin/focusout支持冒泡的兼容后面會詳解)

type = ( selector ? special.delegateType : special.bindType ) || type;

  然后,根據新得到的type類型(focusin)獲取新的special     

 special = jQuery.event.special[ type ] || {};  

獲取的special結果為

jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {  var attaches = 0,  handler = function( event ) {    //模擬冒泡    jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );  };  jQuery.event.special[ fix ] = {        setup: function() {          if ( attaches++ === 0 ) {            document.addEventListener( orig, handler, true );          }        },        teardown: function() {          if ( --attaches === 0 ) {            document.removeEventListener( orig, handler, true );          }        }  };});

  再然后,就是綁定事件,綁定事件實際上就對focusin、focusout做了兼容處理,源碼中第一個判斷有special.setup.call(…)這段代碼,根據上面setup函數可見第一次進入的時候實際上是通過setup函數中的document.addEventListener( orig, handler, true )綁定事件,注意:第一個參數是是orig,因為火狐不支持focusin/focusout所以jQuery使用focus/blur替代來監聽事件;注意第三個參數是true,表示在事件捕獲階段觸發事件。

  我們知道任何瀏覽器捕獲都是從外層到精確的節點的,所有的focusin事件都會被捕獲到,然后執行handler函數(里面是jQuery.event.simulate函數,源碼略)。其他事件綁定則進入if分支將事件直接綁定到elem上

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {  if ( elem.addEventListener ) {    elem.addEventListener( type, eventHandle, false );   } else if ( elem.attachEvent ) {    elem.attachEvent( "on" + type, eventHandle );  }}

special第二組:mouseenter/mouseleave

//使用mouseover/out和事件時機檢測創建mouseenter/leave事件jQuery.each({  mouseenter: "mouseover",  mouseleave: "mouseout"  }, function( orig, fix ) {    jQuery.event.special[ orig ] = {      delegateType: fix,      bindType: fix,      handle: function( event ) {        var ret,        target = this,        related = event.relatedTarget,        handleObj = event.handleObj;        //對于mousenter/leave,當related在target外面的時候才調用handler        //參考: 當鼠標離開/進入瀏覽器窗口的時候是沒有relatedTarget的        if ( !related || (related !== target && !jQuery.contains( target, related )) ) {          event.type = handleObj.origType;          ret = handleObj.handler.apply( this, arguments );          event.type = fix;        }        return ret;      }    };});

  需要注意的是只有在鼠標指針穿過被選元素時,才會觸發 mouseenter 事件。對應mouseleave這樣的話,mouseenter子元素不會反復觸發事件,否則在IE中經常有閃爍情況發生

  使用mouseover/out和事件時機檢測創建mouseenter/leave事件有個關鍵的判斷

if ( !related || (related !== target && !jQuery.contains( target, related )) )

  其中!jQuery.contains( target, related )表示related在target外面。我們使用圖例來解釋

  我們假設處理的是mouseenter事件,進入target。

  鼠標從related到target,很明顯related在target外面,所以當鼠標移動到target的時候滿足條件,調用處理。

   

  現在反過來,很明顯related在target里面,那么鼠標之前就處于mouseenter狀態(意味著之前就進行了mouseenter處理器處理),避免重復調用當然是不進行任何處理直接返回了。

  

  我們假設處理的是mouseleave事件,離開target。

  鼠標從target到related,很明顯related在target里面,所以當鼠標移動到related的時候依然么有離開target,不做處理。

  

  鼠標從target到related,很明顯related在target外面,所以當鼠標移動到related的時候已經離開了target的范圍,做處理。

  

special第三組:submit和change

主要是ie下submit不能冒泡的處理

  jQuery.event.special.submit主要有一下幾個特征

  setup
  postDispatch
  teardown

  根據添加事件的代碼可知添加事件的時候如果符合條件則會調用setup來添加事件

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false )

  jQuery在ie下模擬submit事件以click和keypress替代,只不過是添加了命名空間來區別和普通click和keypress事件。

setup: function() {  ...  jQuery.event.add( this, "click._submit keypress._submit", function( e ) {    var elem = e.target,    form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;      if ( form && !jQuery._data( form, "submitBubbles" ) ) {        jQuery.event.add( form, "submit._submit", function( event ) {          event._submit_bubble = true;        });        jQuery._data( form, "submitBubbles", true );      }  });},

  在事件調用過程中(dispatch)會調用postDispatch來處理

if ( special.postDispatch ) {    special.postDispatch.call( this, event );}  postDispatch中調用simulate完成事件處理postDispatch: function( event ) {  // If form was submitted by the user, bubble the event up the tree  if ( event._submit_bubble ) {    delete event._submit_bubble;    if ( this.parentNode && !event.isTrigger ) {      jQuery.event.simulate( "submit", this.parentNode, event, true );    }  }},

  teardown用在刪除事件綁定中

  ie下change事件的處理和submit類似,事件使用beforeactivate替代來監聽,處理函數變成了handle,在事件分發(dispatch)中執行代碼

ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )    .apply( matched.elem, args ); 

主要源碼如下

 jQuery.event.special.change = {      setup: function() {        //rformElems = /^(?:input|select|textarea)$/i        if ( rformElems.test( this.nodeName ) ) {          // IE不會在check/radio失焦前觸發change事件; 在屬性更改后觸發它的click事件          // 在special.change.handle中會吞掉失焦觸發的change事件.          // 這里任然會在check/radio失焦后觸發onchange事件.          if ( this.type === "checkbox" || this.type === "radio" ) {            jQuery.event.add( this, "propertychange._change", function( event ) {              if ( event.originalEvent.propertyName === "checked" ) {                this._just_changed = true;              }            });            jQuery.event.add( this, "click._change", function( event ) {              if ( this._just_changed && !event.isTrigger ) {                this._just_changed = false;              }              // Allow triggered, simulated change events (#11500)              jQuery.event.simulate( "change", this, event, true );            });          }          return false;        }        // 事件代理; 懶惰模式為后代input節點添加change事件處理        jQuery.event.add( this, "beforeactivate._change", function( e ) {          var elem = e.target;          if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {            jQuery.event.add( elem, "change._change", function( event ) {              if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {                jQuery.event.simulate( "change", this.parentNode, event, true );              }            });            jQuery._data( elem, "changeBubbles", true );          }        });      },      handle: function( event ) {        var elem = event.target;        // 吞掉本地單選框和復選框的change事件,我們在上面已經出發了事件        if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {          return event.handleObj.handler.apply( this, arguments );        }      },    }

  OK,到此,事件系統也告一個段落了,謝謝大家多多支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 欧日韩在线| 精品国产乱码久久久久久久 | 成人午夜在线免费 | 日韩视频一区二区三区四区 | 主播粉嫩国产在线精品 | 国产精品无码久久久久 | 亚洲一级电影在线观看 | 又黄又爽免费无遮挡在线观看 | 亚洲嫩草av | 欧美一级高清片在线 | 一级毛片在线免费播放 | 黄污网站在线观看 | 91久久另类重口变态 | 欧美黄一区 | 美女毛片儿 | 日本残忍极度灌浣肠视频 | 久久久久国产成人免费精品免费 | 一级观看免费完整版视频 | 性生活视频软件 | 久久精品国产99国产精品澳门 | 88xx成人永久免费观看 | 蜜桃视频在线免费观看 | 欧美日韩亚洲成人 | 神秘电影91 | h视频免费在线观看 | 午夜啪视频 | 欧美三级欧美成人高清www | 国产人成精品综合欧美成人 | 国产精品久久久久久久久久大牛 | 91久久91久久精品免观看 | 偿还电影免费 | 9191色| gril hd| 手机视频在线播放 | 亚洲人成电影在线 | 亚洲无av| 视频一区二区精品 | 98国内自拍在线视频 | hd性videos意大利复古 | 国产资源视频在线观看 | hdhdhd69ⅹxxx黑人|