讀Zepto源碼之Event模塊

Event 模塊是 Zepto 必備的模塊之一,因爲對 Event Api 不太熟,Event 對象也比較複雜,因此乍一看 Event 模塊的源碼,有點懵,細看下去,其實也不太複雜。javascript

讀Zepto源碼系列文章已經放到了github上,歡迎star: reading-zeptohtml

源碼版本

本文閱讀的源碼爲 zepto1.2.0java

準備知識

focus/blur 的事件模擬

爲何要對 focusblur 事件進行模擬呢?從 MDN 中能夠看到, focus 事件和 blur 事件並不支持事件冒泡。不支持事件冒泡帶來的直接後果是不能進行事件委託,因此須要對 focusblur 事件進行模擬。git

除了 focus 事件和 blur 事件外,現代瀏覽器還支持 focusin 事件和 focusout 事件,他們和 focus 事件及 blur 事件的最主要區別是支持事件冒泡。所以能夠用 focusin 和模擬 focus 事件的冒泡行爲,用 focusout 事件來模擬 blur 事件的冒泡行爲。github

咱們能夠經過如下代碼來肯定這四個事件的執行順序:web

<input id="test" type="text" />
const target = document.getElementById('test')
target.addEventListener('focusin', () => {console.log('focusin')})
target.addEventListener('focus', () => {console.log('focus')})
target.addEventListener('blur', () => {console.log('blur')})
target.addEventListener('focusout', () => {console.log('focusout')})

chrome59下, input 聚焦和失焦時,控制檯會打印出以下結果:正則表達式

'focus'
'focusin'
'blur'
'focusout'

能夠看到,在此瀏覽器中,事件的執行順序應該是 focus > focusin > blur > focusoutchrome

關於這幾個事件更詳細的描述,能夠查看:《說說focus /focusin /focusout /blur 事件segmentfault

關於事件的執行順序,我測試的結果與文章所說的有點不太同樣。感興趣的能夠點擊這個連接測試下http://jsbin.com/nizugazamo/edit?html,js,console,output。不過我以爲執行順序能夠沒必要細究,能夠將 focusin 做爲 focus 事件的冒泡版本。數組

mouseenter/mouseleave 的事件模擬

focusblur 同樣,mouseentermouseleave 也不支持事件的冒泡, 可是 mouseovermouseout 支持事件冒泡,所以,這兩個事件的冒泡處理也能夠分別用 mouseovermouseout 來模擬。

在鼠標事件的 event 對象中,有一個 relatedTarget 的屬性,從 MDN:MouseEvent.relatedTarget 文檔中,能夠看到,mouseoverrelatedTarget 指向的是移到目標節點上時所離開的節點( exited from ),mouseoutrelatedTarget 所指向的是離開所在的節點後所進入的節點( entered to )。

另外 mouseover 事件會隨着鼠標的移動不斷觸發,可是 mouseenter 事件只會在進入節點的那一刻觸發一次。若是鼠標已經在目標節點上,那 mouseover 事件觸發時的 relatedTarget 爲當前節點。

所以,要模擬 mouseentermouseleave 事件,只須要肯定觸發 mouseovermouseout 事件上的 relatedTarget 不存在,或者 relatedTarget 不爲當前節點,而且不爲當前節點的子節點,避免子節點事件冒泡的影響。

關於 mouseentermouseleave 的模擬, 謙龍 有篇文章《mouseenter與mouseover爲什麼這般糾纏不清?》寫得很清楚,建議讀一下。

Event 模塊的核心

Event 模塊簡化後以下:

;(function($){})(Zepto)

其實就是向閉包中傳入 Zepto 對象,而後對 Zepto 對象作一些擴展。

Event 模塊中,主要作了以下幾件事:

  • 提供簡潔的API

  • 統一不一樣瀏覽器的 event 對象

  • 事件句柄緩存池,方便手動觸發事件和解綁事件。

  • 事件委託

內部方法

zid

var _zid = 1
function zid(element) {
  return element._zid || (element._zid = _zid++)
}

獲取參數 element 對象的 _zid 屬性,若是屬性不存在,則全局變量 _zid 增長 1 ,做爲 element_zid 的屬性值返回。這個方法用來標記已經綁定過事件的元素,方便查找。

parse

function parse(event) {
  var parts = ('' + event).split('.')
  return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
}

zepto 中,支持事件的命名空間,能夠用 eventType.ns1.ns2... 的形式來給事件添加一個或多個命名空間。

parse 函數用來分解事件名和命名空間。

'' + event 是將 event 變成字符串,再以 . 分割成數組。

返回的對象中,e 爲事件名, ns 爲排序後,以空格相連的命名空間字符串,形如 ns1 ns2 ns3 ... 的形式。

matcherFor

function matcherFor(ns) {
  return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
}

生成匹配命名空間的表達式,例如,傳進來的參數 nsns1 ns2 ns3 ,最終生成的正則爲 /(?:^| )ns1.* ?ns2.* ?ns3(?: |$)/。至於有什麼用,下面立刻講到。

findHandlers,查找緩存的句柄

handlers = {}
function findHandlers(element, event, fn, selector) {
  event = parse(event)
  if (event.ns) var matcher = matcherFor(event.ns)
  return (handlers[zid(element)] || []).filter(function(handler) {
    return handler
      && (!event.e  || handler.e == event.e)
      && (!event.ns || matcher.test(handler.ns))
      && (!fn       || zid(handler.fn) === zid(fn))
      && (!selector || handler.sel == selector)
  })
}

查找元素對應的事件句柄。

event = parse(event)

調用 parse 函數,分隔出 event 參數的事件名和命名空間。

if (event.ns) var matcher = matcherFor(event.ns)

若是命名空間存在,則生成匹配該命名空間的正則表達式 matcher

return (handlers[zid(element)] || []).filter(function(handler) {
    ...
  })

返回的實際上是 handlers[zid(element)] 中符合條件的句柄函數。 handlers 是緩存的句柄容器,用 element_zid 屬性值做爲 key

return handler  // 條件1
     && (!event.e  || handler.e == event.e) // 條件2
     && (!event.ns || matcher.test(handler.ns)) // 條件3
     && (!fn       || zid(handler.fn) === zid(fn)) // 條件4
     && (!selector || handler.sel == selector) // 條件5

返回的句柄必須知足5個條件:

  1. 句柄必須存在

  2. 若是 event.e 存在,則句柄的事件名必須與 event 的事件名一致

  3. 若是命名空間存在,則句柄的命名空間必需要與事件的命名空間匹配( matcherFor 的做用 )

  4. 若是指定匹配的事件句柄爲 fn ,則當前句柄 handler_zid 必須與指定的句柄 fn 相一致

  5. 若是指定選擇器 selector ,則當前句柄中的選擇器必須與指定的選擇器一致

從上面的比較能夠看到,緩存的句柄對象的形式以下:

{
  fn: '', // 函數
  e: '', // 事件名
  ns: '', // 命名空間
  sel: '',  // 選擇器
  // 除此以外,其實還有
  i: '', // 函數索引
  del: '', // 委託函數
  proxy: '', // 代理函數
  // 後面這幾個屬性會講到
}

realEvent,返回對應的冒泡事件

focusinSupported = 'onfocusin' in window,
focus = { focus: 'focusin', blur: 'focusout' },
hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' }
function realEvent(type) {
  return hover[type] || (focusinSupported && focus[type]) || type
}

這個函數實際上是將 focus/blur 轉換成 focusin/focusout ,將 mouseenter/mouseleave 轉換成 mouseover/mouseout 事件。

因爲 focusin/focusout 事件瀏覽器支持程度還不是很好,所以要對瀏覽器支持作一個檢測,若是瀏覽器支持,則返回,不然,返回原事件名。

compatible,修正event對象

returnTrue = function(){return true},
returnFalse = function(){return false},
eventMethods = {
  preventDefault: 'isDefaultPrevented',
  stopImmediatePropagation: 'isImmediatePropagationStopped',
  stopPropagation: 'isPropagationStopped'
}

function compatible(event, source) {
  if (source || !event.isDefaultPrevented) {
    source || (source = event)

    $.each(eventMethods, function(name, predicate) {
      var sourceMethod = source[name]
      event[name] = function(){
        this[predicate] = returnTrue
        return sourceMethod && sourceMethod.apply(source, arguments)
      }
      event[predicate] = returnFalse
    })

    try {
      event.timeStamp || (event.timeStamp = Date.now())
    } catch (ignored) { }

    if (source.defaultPrevented !== undefined ? source.defaultPrevented :
        'returnValue' in source ? source.returnValue === false :
        source.getPreventDefault && source.getPreventDefault())
      event.isDefaultPrevented = returnTrue
      }
  return event
}

compatible 函數用來修正 event 對象的瀏覽器差別,向 event 對象中添加了 isDefaultPreventedisImmediatePropagationStoppedisPropagationStopped 幾個方法,對不支持 timeStamp 的瀏覽器,向 event 對象中添加 timeStamp 屬性。

if (source || !event.isDefaultPrevented) {
  source || (source = event)

  $.each(eventMethods, function(name, predicate) {
    var sourceMethod = source[name]
    event[name] = function(){
      this[predicate] = returnTrue
      return sourceMethod && sourceMethod.apply(source, arguments)
    }
    event[predicate] = returnFalse
  })

判斷條件是,原事件對象存在,或者事件 eventisDefaultPrevented 不存在時成立。

若是 source 不存在,則將 event 賦值給 source, 做爲原事件對象。

遍歷 eventMethods ,得到原事件對象的對應方法名 sourceMethod

event[name] = function(){
  this[predicate] = returnTrue
  return sourceMethod && sourceMethod.apply(source, arguments)
}

改寫 event 對象相應的方法,若是執行對應的方法時,先將事件中方法所對應的新方法賦值爲 returnTrue 函數 ,例如執行 preventDefault 方法時, isDefaultPrevented 方法的返回值爲 true

event[predicate] = returnFalse

這是將新添加的屬性,初始化爲 returnFalse 方法

try {
  event.timeStamp || (event.timeStamp = Date.now())
} catch (ignored) { }

這段向不支持 timeStamp 屬性的瀏覽器中添加 timeStamp 屬性。

if (source.defaultPrevented !== undefined ? source.defaultPrevented :
    'returnValue' in source ? source.returnValue === false :
    source.getPreventDefault && source.getPreventDefault())
  event.isDefaultPrevented = returnTrue
  }

這是對瀏覽器 preventDefault 不一樣實現的兼容。

source.defaultPrevented !== undefined ? source.defaultPrevented : '三元表達式'

若是瀏覽器支持 defaultPrevented, 則返回 defaultPrevented 的值

'returnValue' in source ? source.returnValue === false : '後一個判斷'

returnValue 默認爲 true,若是阻止了瀏覽器的默認行爲, returnValue 會變爲 false

source.getPreventDefault && source.getPreventDefault()

若是瀏覽器支持 getPreventDefault 方法,則調用 getPreventDefault() 方法獲取是否阻止瀏覽器的默認行爲。

判斷爲 true 的時候,將 isDefaultPrevented 設置爲 returnTrue 方法。

createProxy,建立代理對象

ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,
function createProxy(event) {
  var key, proxy = { originalEvent: event }
  for (key in event)
    if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]

    return compatible(proxy, event)
}

zepto 中,事件觸發的時候,返回給咱們的 event 都不是原生的 event 對象,都是代理對象,這個就是代理對象的建立方法。

ignoreProperties 用來排除 A-Z 開頭,即全部大寫字母開頭的屬性,還有以returnValue 結尾,layerX/layerYwebkitMovementX/webkitMovementY 結尾的非標準屬性。

for (key in event)
  if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]

遍歷原生事件對象,排除掉不須要的屬性和值爲 undefined 的屬性,將屬性和值複製到代理對象上。

最終返回的是修正後的代理對象

eventCapture

function eventCapture(handler, captureSetting) {
  return handler.del &&
    (!focusinSupported && (handler.e in focus)) ||
    !!captureSetting
}

返回 true 表示在捕獲階段執行事件句柄,不然在冒泡階段執行。

若是存在事件代理,而且事件爲 focus/blur 事件,在瀏覽器不支持 focusin/focusout 事件時,設置爲 true , 在捕獲階段處理事件,間接達到冒泡的目的。

不然做用自定義的 captureSetting 設置事件執行的時機。

add,Event 模塊的核心方法

function add(element, events, fn, data, selector, delegator, capture){
  var id = zid(element), set = (handlers[id] || (handlers[id] = []))
  events.split(/\s/).forEach(function(event){
    if (event == 'ready') return $(document).ready(fn)
    var handler   = parse(event)
    handler.fn    = fn
    handler.sel   = selector
    // emulate mouseenter, mouseleave
    if (handler.e in hover) fn = function(e){
      var related = e.relatedTarget
      if (!related || (related !== this && !$.contains(this, related)))
        return handler.fn.apply(this, arguments)
        }
    handler.del   = delegator
    var callback  = delegator || fn
    handler.proxy = function(e){
      e = compatible(e)
      if (e.isImmediatePropagationStopped()) return
      e.data = data
      var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
      if (result === false) e.preventDefault(), e.stopPropagation()
      return result
    }
    handler.i = set.length
    set.push(handler)
    if ('addEventListener' in element)
      element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
}

add 方法是向元素添加事件及事件響應,參數比較多,先來看看各參數的含義:

element // 事件綁定的元素
events // 須要綁定的事件列表
fn // 事件執行時的句柄
data // 事件執行時,傳遞給事件對象的數據
selector // 事件綁定元素的選擇器
delegator // 事件委託函數 
capture // 那個階段執行事件句柄
var id = zid(element), set = (handlers[id] || (handlers[id] = []))

獲取或設置 idset 爲事件句柄容器。

events.split(/\s/).forEach(function(event){})

對每一個事件進行處理

if (event == 'ready') return $(document).ready(fn)

若是爲 ready 事件,則調用 ready 方法,停止後續的執行

var handler   = parse(event)
handler.fn    = fn
handler.sel   = selector
// emulate mouseenter, mouseleave
if (handler.e in hover) fn = function(e){
  var related = e.relatedTarget
  if (!related || (related !== this && !$.contains(this, related)))
    return handler.fn.apply(this, arguments)
    }
handler.del   = delegator
var callback  = delegator || fn

這段代碼是設置 handler 上的一些屬性,緩存起來。

這裏主要看對 mouseentermouseleave 事件的模擬,具體的原理上面已經說過,只有在條件成立的時候纔會執行事件句柄。

handler.proxy = function(e){
  e = compatible(e)
  if (e.isImmediatePropagationStopped()) return
  e.data = data
  var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args))
  if (result === false) e.preventDefault(), e.stopPropagation()
  return result
}

事件句柄的代理函數。

e 爲事件執行時的原生 event 對象,所以先調用 compatiblee 進行修正。

調用 isImmediatePropagationStopped 方法,看是否已經執行過 stopImmediatePropagation 方法,若是已經執行,則停止後續程序的執行。

再擴展 e 對象,將 data 存到 edata 屬性上。

執行事件句柄,將 e 對象做爲句柄的第一個參數。

若是執行完畢後,顯式返回 false,則阻止瀏覽器的默認行爲和事件冒泡。

set.push(handler)
if ('addEventListener' in element)
  element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))

將句柄存入句柄容器

調用元素的 addEventListener 方法,添加事件,事件的回調函數用的是句柄的代理函數,eventCapture(handler, capture) 來用指定是否在捕獲階段執行。

remove,刪除事件

function remove(element, events, fn, selector, capture){
  var id = zid(element)
  ;(events || '').split(/\s/).forEach(function(event){
    findHandlers(element, event, fn, selector).forEach(function(handler){
      delete handlers[id][handler.i]
      if ('removeEventListener' in element)
        element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
        })
  })
}

首先獲取指定元素的 _zid

;(events || '').split(/\s/).forEach(function(event){})

遍歷須要刪除的 events

findHandlers(element, event, fn, selector).forEach(function(handler){})

調用 findHandlers 方法,查找 event 下須要刪除的事件句柄

delete handlers[id][handler.i]

刪除句柄容器中對應的事件,在 add 函數中的句柄對象中的 i 屬性就用在這裏了,方便查找須要刪除的句柄。

element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))

調用 removeEventListener 方法,刪除對應的事件。

工具函數

$.event

$.event = { add: add, remove: remove }

add 方法和 remove 方法暴露出去,應該是方便第三方插件作擴展

$.proxy

$.proxy = function(fn, context) {
  var args = (2 in arguments) && slice.call(arguments, 2)
  if (isFunction(fn)) {
    var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
    proxyFn._zid = zid(fn)
    return proxyFn
  } else if (isString(context)) {
    if (args) {
      args.unshift(fn[context], fn)
      return $.proxy.apply(null, args)
    } else {
      return $.proxy(fn[context], fn)
    }
  } else {
    throw new TypeError("expected function")
  }
}

代理函數,做用有點像 JS 中的 bind 方法,返回的是一個代理後改變執行上下文的函數。

var args = (2 in arguments) && slice.call(arguments, 2)

若是提供超過3個參數,則去除前兩個參數,將後面的參數做爲執行函數 fn 的參數。

if (isFunction(fn)) {
  var proxyFn = function(){ return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }
  proxyFn._zid = zid(fn)
  return proxyFn
}

proxy 的執行函數有兩種傳遞方式,一是在第一個參數直接傳入,二是第一個參數爲上下文對象,執行函數也在上下文對象中一塊兒傳入。

這裏判斷 fn 是否爲函數,即第一種傳參方式,調用 fn 函數的 apply 方法,將上下文對象 context 做爲 apply 的第一個參數,若是 args 存在,則與 fn 的參數合併。

給代理後的函數加上 _zid 屬性,方便函數的查找。

else if (isString(context)) {
  if (args) {
    args.unshift(fn[context], fn)
    return $.proxy.apply(null, args)
  } else {
    return $.proxy(fn[context], fn)
  }

若是函數已經包含在上下文對象中,即第一個參數 fn 爲對象,第二個參數 context 爲字符串,用來指定執行函數的在上下文對象中的屬性名。

if (args) {
  args.unshift(fn[context], fn)
  return $.proxy.apply(null, args)
}

若是參數存在時,將 fn[context] ,也即執行函數和 fn ,也即上下文對象放入 args 數組的開頭,這樣就將參數修正成跟第一種傳參方式同樣,再調用 $.proxy 函數。這裏調用 apply 方法,是由於不知道參數有多少個,調用 apply 能夠以數組的形式傳入。

若是 args 不存在時,肯定的參數項只有兩個,所以能夠直接調用 $.proxy 方法。

$.Event

specialEvents={},
specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'

$.Event = function(type, props) {
  if (!isString(type)) props = type, type = props.type
  var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
  if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
  event.initEvent(type, bubbles, true)
  return compatible(event)
}

specialEvents 是將鼠標事件修正爲 MouseEvents ,這應該是處理瀏覽器的兼容問題,可能有些瀏覽器中,這些事件的事件類型並非 MouseEvents

$.Event 方法用來手動建立特定類型的事件。

參數 type 能夠爲字符串,也能夠爲 event 對象。props 爲擴展 event 對象的對象。

if (!isString(type)) props = type, type = props.type

若是不是字符串,也便是 event 對象時,將 type 賦給 propstype 爲當前 event 對象中的 type 屬性值。

var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true

調用 createEvent 方法,建立對應類型的 event 事件,並將事件冒泡默認設置爲 true

if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])

遍歷 props 屬性,若是有指定 bubbles ,則採用指定的冒泡行爲,其餘屬性複製到 event 對象上,實現對 event 對象的擴展。

event.initEvent(type, bubbles, true)
return compatible(event)

初始化新建立的事件,並將修正後的事件對象返回。

方法

.on()

$.fn.on = function(event, selector, data, callback, one){
  var autoRemove, delegator, $this = this
  if (event && !isString(event)) {
    $.each(event, function(type, fn){
      $this.on(type, selector, data, fn, one)
    })
    return $this
  }

  if (!isString(selector) && !isFunction(callback) && callback !== false)
    callback = data, data = selector, selector = undefined
    if (callback === undefined || data === false)
      callback = data, data = undefined

      if (callback === false) callback = returnFalse

      return $this.each(function(_, element){
        if (one) autoRemove = function(e){
          remove(element, e.type, callback)
          return callback.apply(this, arguments)
        }

        if (selector) delegator = function(e){
          var evt, match = $(e.target).closest(selector, element).get(0)
          if (match && match !== element) {
            evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
            return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
          }
        }

        add(element, event, callback, data, selector, delegator || autoRemove)
      })
}

on 方法來用給元素綁定事件,最終調用的是 add 方法,前面的一大段邏輯主要是修正參數。

var autoRemove, delegator, $this = this
if (event && !isString(event)) {
  $.each(event, function(type, fn){
    $this.on(type, selector, data, fn, one)
  })
  return $this
}

autoRemove 表示在執行完事件響應後,自動解綁的函數。

event 能夠爲字符串或者對象,當爲對象時,對象的屬性爲事件類型,屬性值爲句柄。

這段是處理 event 爲對象時的狀況,遍歷對象,獲得事件類型和句柄,而後再次調用 on 方法,繼續修正後續的參數。

if (!isString(selector) && !isFunction(callback) && callback !== false)
  callback = data, data = selector, selector = undefined
if (callback === undefined || data === false)
  callback = data, data = undefined

if (callback === false) callback = returnFalse

先來分析第一個 ifselector 不爲 stringcallback 不爲函數,而且 callback 不爲 false 時的狀況。

這裏能夠肯定 selector 並無傳遞,由於 selector 不是必傳的參數。

所以這裏將 data 賦給 callbackselector 賦給 data ,將 selector 設置爲 undefined ,由於 selector 沒有傳遞,所以相應參數的位置都前移了一位。

再來看第二個 if ,若是 callback( 原來的 data ) 爲 undefineddatafalse 時,表示 selector 沒有傳遞,而且 data 也沒有傳遞,所以將 data 賦給 callback ,將 data 設置爲 undefined ,即將參數再前移一位。

第三個 if ,若是 callback === false ,用 returnFalse 函數代替,若是不用 returnFalse 代替,會報錯。

return $this.each(function(_, element){
  add(element, event, callback, data, selector, delegator || autoRemove)
})

能夠看到,這裏是遍歷元素集合,爲每一個元素都調用 add 方法,綁定事件。

if (one) autoRemove = function(e){
  remove(element, e.type, callback)
  return callback.apply(this, arguments)
}

若是隻調用一次,設置 autoRemove 爲一個函數,這個函數在句柄執行前,調用 remove 方法,將綁定在元素上對應事件解綁。

if (selector) delegator = function(e){
  var evt, match = $(e.target).closest(selector, element).get(0)
  if (match && match !== element) {
    evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
    return (autoRemove || callback).apply(match, [evt].concat(slice.call(arguments, 1)))
  }
}

若是 selector 存在,表示須要作事件代理。

調用 closest 方法,從事件的目標元素 e.target 開始向上查找,返回第一個匹配 selector 的元素。關於 closest 方法,見《讀Zepto源碼之集合元素查找》分析。

若是 match 存在,而且 match 不爲當前元素,則調用 createProxy 方法,爲當前事件對象建立代理對象,再調用 $.extend 方法,爲代理對象擴展 currentTargetliveFired 屬性,將代理元素和觸發事件的元素保存到事件對象中。

最後執行句柄函數,以代理元素 match 做爲句柄的上下文,用代理後的 event 對象 evt 替換掉原句柄函數的第一個參數。

將該函數賦給 delegator ,做爲代理函數傳遞給 add 方法。

.off()

$.fn.off = function(event, selector, callback){
  var $this = this
  if (event && !isString(event)) {
    $.each(event, function(type, fn){
      $this.off(type, selector, fn)
    })
    return $this
  }

  if (!isString(selector) && !isFunction(callback) && callback !== false)
    callback = selector, selector = undefined

    if (callback === false) callback = returnFalse

    return $this.each(function(){
      remove(this, event, callback, selector)
    })
}

解綁事件

if (event && !isString(event)) {
  $.each(event, function(type, fn){
    $this.off(type, selector, fn)
  })
  return $this
}

這段邏輯與 on 方法中的類似,修正參數,再也不細說。

if (!isString(selector) && !isFunction(callback) && callback !== false)
  callback = selector, selector = undefined
if (callback === false) callback = returnFalse

第一個 if 是處理 selector 參數沒有傳遞的狀況的, selector 位置傳遞的實際上是 callback

第二個 if 是判斷若是 callbackfalse ,將 callback 賦值爲 returnFalse 函數。

return $this.each(function(){
  remove(this, event, callback, selector)
})

最後遍歷全部元素,調用 remove 函數,爲每一個元素解綁事件。

.bind()

$.fn.bind = function(event, data, callback){
  return this.on(event, data, callback)
}

bind 方法內部調用的實際上是 on 方法。

.unbind()

$.fn.unbind = function(event, callback){
  return this.off(event, callback)
}

unbind 方法內部調用的是 off 方法。

.one()

$.fn.one = function(event, selector, data, callback){
  return this.on(event, selector, data, callback, 1)
}

one 方法內部調用的也是 on 方法,只不過默認傳遞了 one 參數爲 1 ,表示綁定的事件只執行一下。

.delegate()

$.fn.delegate = function(selector, event, callback){
  return this.on(event, selector, callback)
}

事件委託,也是調用 on 方法,只是 selector 必定要傳遞。

.undelegate()

$.fn.undelegate = function(selector, event, callback){
  return this.off(event, selector, callback)
}

取消事件委託,內部調用的是 off 方法,selector 必需要傳遞。

.live()

$.fn.live = function(event, callback){
  $(document.body).delegate(this.selector, event, callback)
  return this
}

動態建立的節點也能夠響應事件。其實事件綁定在 body 上,而後委託到當前節點上。內部調用的是 delegate 方法。

.die()

$.fn.die = function(event, callback){
  $(document.body).undelegate(this.selector, event, callback)
  return this
}

將由 live 綁定在 body 上的事件銷燬,內部調用的是 undelegate 方法。

.triggerHandler()

$.fn.triggerHandler = function(event, args){
  var e, result
  this.each(function(i, element){
    e = createProxy(isString(event) ? $.Event(event) : event)
    e._args = args
    e.target = element
    $.each(findHandlers(element, event.type || event), function(i, handler){
      result = handler.proxy(e)
      if (e.isImmediatePropagationStopped()) return false
        })
  })
  return result
}

直接觸發事件回調函數。

參數 event 能夠爲事件類型字符串,也能夠爲 event 對象。

e = createProxy(isString(event) ? $.Event(event) : event)

若是 event 爲字符串時,則調用 $.Event 工具函數來初始化一個事件對象,再調用 createProxy 來建立一個 event 代理對象。

$.each(findHandlers(element, event.type || event), function(i, handler){
  result = handler.proxy(e)
  if (e.isImmediatePropagationStopped()) return false
    })

調用 findHandlers 方法來找出事件的全部句柄,調用 proxy 方法,即真正綁定到事件上的回調函數(參見 add 的解釋),拿到方法返回的結果 result ,並查看 isImmediatePropagationStopped 返回的結果是否爲 true ,若是是,馬上停止後續執行。

若是返回的結果 resultfalse ,也馬上停止後續執行。

因爲 triggerHandler 直接觸發回調函數,因此事件不會冒泡。

.trigger()

$.fn.trigger = function(event, args){
  event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)
  event._args = args
  return this.each(function(){
    // handle focus(), blur() by calling them directly
    if (event.type in focus && typeof this[event.type] == "function") this[event.type]()
    // items in the collection might not be DOM elements
    else if ('dispatchEvent' in this) this.dispatchEvent(event)
    else $(this).triggerHandler(event, args)
      })
}

手動觸發事件。

event = (isString(event) || $.isPlainObject(event)) ? $.Event(event) : compatible(event)

event 能夠傳遞事件類型,對象和 event 對象。

若是傳遞的是字符串或者純粹對象,則先調用 $.Event 方法來初始化事件,不然調用 compatible 方法來修正 event 對象,因爲 $.Event 方法在內部其實已經調用過 compatible 方法修正 event 對象了的,因此外部不須要再調用一次。

if (event.type in focus && typeof this[event.type] == "function") this[event.type]()

若是是 focus/blur 方法,則直接調用 this.focus()this.blur() 方法,這兩個方法是瀏覽器原生支持的。

若是 thisDOM 元素,即存在 dispatchEvent 方法,則用 dispatchEvent 來觸發事件,關於 dispatchEvent ,能夠參考 MDN: EventTarget.dispatchEvent()

不然,直接調用 triggerHandler 方法來觸發事件的回調函數。

因爲 trigger 是經過觸發事件來執行事件句柄的,所以事件會冒泡。

系列文章

  1. 讀Zepto源碼之代碼結構

  2. 讀 Zepto 源碼以內部方法

  3. 讀Zepto源碼之工具函數

  4. 讀Zepto源碼之神奇的$

  5. 讀Zepto源碼之集合操做

  6. 讀Zepto源碼之集合元素查找

  7. 讀Zepto源碼之操做DOM

  8. 讀Zepto源碼之樣式操做

  9. 讀Zepto源碼之屬性操做

參考

License

License: CC BY-NC-ND 4.0

最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:

做者:對角另外一面

相關文章
相關標籤/搜索