读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

最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

做者:对角另外一面

相关文章
相关标签/搜索