读Zepto源码之Event模块

Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较庞杂,所以乍一看 Event 模块的源码,有点懵,细看下去,实在也不太庞杂。

读Zepto源码系列文章已放到了github上,迎接star: reading-zepto

源码版本

本文浏览的源码为 zepto1.2.0

预备学问

focus/blur 的事宜模仿

为什么要对 focusblur 事宜举行模仿呢?从 MDN 中能够看到, focus 事宜和 blur 事宜并不支撑事宜冒泡。不支撑事宜冒泡带来的直接效果是不能举行事宜托付,所以需要对 focusblur 事宜举行模仿。

除了 focus 事宜和 blur 事宜外,当代浏览器还支撑 focusin 事宜和 focusout 事宜,他们和 focus 事宜及 blur 事宜的最重要区别是支撑事宜冒泡。因而能够用 focusin 和模仿 focus 事宜的冒泡行动,用 focusout 事宜来模仿 blur 事宜的冒泡行动。

我们能够经由过程以下代码来肯定这四个事宜的实行递次:

<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 > focusout

关于这几个事宜更细致的形貌,能够检察:《说说focus /focusin /focusout /blur 事宜

关于事宜的实行递次,我测试的效果与文章所说的有点不太一样。感兴趣的能够点击这个链接测试下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

《读Zepto源码之Event模块》

末了,一切文章都邑同步发送到微信民众号上,迎接关注,迎接提意见: 《读Zepto源码之Event模块》

作者:对角另一面

    原文作者:对角另一面
    原文地址: https://segmentfault.com/a/1190000010228427
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞