欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

jQuery 1.9.1源碼分析系列(十)事件系統(tǒng)之主動(dòng)觸發(fā)事件和模擬冒泡處理

 更新時(shí)間:2015年11月24日 13:53:24   作者:chua1989  
這篇文章主要介紹了jQuery 1.9.1源碼分析系列(十)事件系統(tǒng)之主動(dòng)觸發(fā)事件和模擬冒泡處理的相關(guān)資料,需要的朋友可以參考下

發(fā)現(xiàn)一個(gè)小點(diǎn),先前沒有注意的

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

  jQuery重載stopPropagation函數(shù)調(diào)用的本地事件對(duì)象的stopPropagation函數(shù)阻止冒泡。也就是說,阻止冒泡的是當(dāng)前節(jié)點(diǎn),而不是事件源。

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

a.觸發(fā)事件低級(jí)API——jQuery.event.trigger

  trigger函數(shù)對(duì)所有類型事件的觸發(fā)提供了支持。這些事件主要分為兩類:普通瀏覽器事件(包含帶有命名空間的事件如"click.chua")、自定義事件。因?yàn)橐y(tǒng)一處理,所以函數(shù)內(nèi)部實(shí)現(xiàn)沒有調(diào)用.click()這種方式來對(duì)普通瀏覽器事件做捷徑處理,而是統(tǒng)一流程。處理過程如下

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

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

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

  if ( type.indexOf(".") >= 0 ) {
        //有命名空間的事件觸發(fā); 先取出事件處理入口函數(shù)handle()使用的事件類型type
        namespaces = type.split(".");
        type = namespaces.shift();
        namespaces.sort();
      }
      ...// 調(diào)用者可以傳遞jQuery.Event對(duì)象,普通對(duì)象,甚至是字符串
      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("\\.(?:.*\\.|)") + "(\\.|$)" ) :
      null;
      // 重置result屬性,避免上次的結(jié)果殘留
      event.result = undefined;
      if ( !event.target ) {
        event.target = elem;
      }
      // 克隆傳參data并將event放在傳參data的前面,創(chuàng)建出事件處理入口函數(shù)的參數(shù)列表,創(chuàng)建后結(jié)果可能是[event,data]
      data = data == null ?
      [ event ] :
      jQuery.makeArray( data, [ event ] );

  后面這段組合事件處理參數(shù)列表data在后面處理時(shí)調(diào)用

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

  3.判斷是否是特殊節(jié)點(diǎn)對(duì)象的的特殊事件,是的話特殊處理

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

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

 special: {
      click.trigger: function(){   // checkbox, 觸發(fā)本地事件確保狀態(tài)正確if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
            this.click();
            return false;
          }
      },
      focus.trigger: function() {  // 觸發(fā)本地事件保證失焦/聚焦序列正確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.從事件源開始遍歷父節(jié)點(diǎn)直到Window對(duì)象,將經(jīng)過的節(jié)點(diǎn)保存(保存到eventPath)下來備用

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

  5.循環(huán)先前保存的節(jié)點(diǎn),訪問節(jié)點(diǎn)緩存,如果有對(duì)應(yīng)的事件類型處理隊(duì)列則取出其綁定的事件(入口函數(shù))進(jìn)行調(diào)用。      

// jQuery綁定函數(shù)處理:判斷節(jié)點(diǎn)緩存中是否保存相應(yīng)的事件處理函數(shù),如果有則執(zhí)行
       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. 最后處理瀏覽器默認(rèn)事件,比如submit標(biāo)簽的提交表單處理。

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

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

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

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

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

  具體有:

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

  看一下模擬冒泡的函數(shù)simulate

simulate: function( type, elem, event, bubble ) {
      // 構(gòu)建一個(gè)新的事件以區(qū)別先前綁定的事件.
      // 新構(gòu)建的事件避免阻止冒泡, 但如果模擬事件可以阻止默認(rèn)操作的話,我們做同樣的阻止默認(rèn)操作。
      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();
      }
    }

  看到?jīng)]有,真正模擬冒泡函數(shù)是jQuery.event.trigger函數(shù)

special第一組

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

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

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

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

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

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

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

獲取的special結(jié)果為

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 );
          }
        }
  };
});

  再然后,就是綁定事件,綁定事件實(shí)際上就對(duì)focusin、focusout做了兼容處理,源碼中第一個(gè)判斷有special.setup.call(…)這段代碼,根據(jù)上面setup函數(shù)可見第一次進(jìn)入的時(shí)候?qū)嶋H上是通過setup函數(shù)中的document.addEventListener( orig, handler, true )綁定事件,注意:第一個(gè)參數(shù)是是orig,因?yàn)榛鸷恢С謋ocusin/focusout所以jQuery使用focus/blur替代來監(jiān)聽事件;注意第三個(gè)參數(shù)是true,表示在事件捕獲階段觸發(fā)事件。

  我們知道任何瀏覽器捕獲都是從外層到精確的節(jié)點(diǎn)的,所有的focusin事件都會(huì)被捕獲到,然后執(zhí)行handler函數(shù)(里面是jQuery.event.simulate函數(shù),源碼略)。其他事件綁定則進(jìn)入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和事件時(shí)機(jī)檢測創(chuàng)建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;
        //對(duì)于mousenter/leave,當(dāng)related在target外面的時(shí)候才調(diào)用handler
        //參考: 當(dāng)鼠標(biāo)離開/進(jìn)入瀏覽器窗口的時(shí)候是沒有relatedTarget的
        if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
          event.type = handleObj.origType;
          ret = handleObj.handler.apply( this, arguments );
          event.type = fix;
        }
        return ret;
      }
    };
});

  需要注意的是只有在鼠標(biāo)指針穿過被選元素時(shí),才會(huì)觸發(fā) mouseenter 事件。對(duì)應(yīng)mouseleave這樣的話,mouseenter子元素不會(huì)反復(fù)觸發(fā)事件,否則在IE中經(jīng)常有閃爍情況發(fā)生

  使用mouseover/out和事件時(shí)機(jī)檢測創(chuàng)建mouseenter/leave事件有個(gè)關(guān)鍵的判斷

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

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

  我們假設(shè)處理的是mouseenter事件,進(jìn)入target。

  鼠標(biāo)從related到target,很明顯related在target外面,所以當(dāng)鼠標(biāo)移動(dòng)到target的時(shí)候滿足條件,調(diào)用處理。

   

  現(xiàn)在反過來,很明顯related在target里面,那么鼠標(biāo)之前就處于mouseenter狀態(tài)(意味著之前就進(jìn)行了mouseenter處理器處理),避免重復(fù)調(diào)用當(dāng)然是不進(jìn)行任何處理直接返回了。

  

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

  鼠標(biāo)從target到related,很明顯related在target里面,所以當(dāng)鼠標(biāo)移動(dòng)到related的時(shí)候依然么有離開target,不做處理。

  

  鼠標(biāo)從target到related,很明顯related在target外面,所以當(dāng)鼠標(biāo)移動(dòng)到related的時(shí)候已經(jīng)離開了target的范圍,做處理。

  

special第三組:submit和change

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

  jQuery.event.special.submit主要有一下幾個(gè)特征

  setup
  postDispatch
  teardown

  根據(jù)添加事件的代碼可知添加事件的時(shí)候如果符合條件則會(huì)調(diào)用setup來添加事件

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

  jQuery在ie下模擬submit事件以click和keypress替代,只不過是添加了命名空間來區(qū)別和普通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 );
      }
  });
},

  在事件調(diào)用過程中(dispatch)會(huì)調(diào)用postDispatch來處理

if ( special.postDispatch ) {
    special.postDispatch.call( this, event );
}
  postDispatch中調(diào)用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替代來監(jiān)聽,處理函數(shù)變成了handle,在事件分發(fā)(dispatch)中執(zhí)行代碼

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不會(huì)在check/radio失焦前觸發(fā)change事件; 在屬性更改后觸發(fā)它的click事件
          // 在special.change.handle中會(huì)吞掉失焦觸發(fā)的change事件.
          // 這里任然會(huì)在check/radio失焦后觸發(fā)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節(jié)點(diǎn)添加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;
        // 吞掉本地單選框和復(fù)選框的change事件,我們在上面已經(jīng)出發(fā)了事件
        if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
          return event.handleObj.handler.apply( this, arguments );
        }
      },
    }

  OK,到此,事件系統(tǒng)也告一個(gè)段落了,謝謝大家多多支持。

相關(guān)文章

最新評(píng)論