Zepto源码学习Event模块

栏目: Node.js · 发布时间: 5年前

内容简介:为什么要看Zepto的源码,因为公司用的是这个。。。。再看这个源码的过程中,因为对事件类型的不充分,导致学习起来有些费劲,所以在讲这个板块之前先对一些事件进行了解。下面是触发点击事件的代码,我们在inner上添加点击事件,在wrapper添加事件,点击inner都会触发click事件。但这种情况需要我们每次都去点击回调函数才会执行,有没有函数不需要我们手动触发,自动触发呢?

为什么要看Zepto的源码,因为公司用的是这个。。。。

再看这个源码的过程中,因为对事件类型的不充分,导致学习起来有些费劲,所以在讲这个板块之前先对一些事件进行了解。

了解基本event信息

事件分发

下面是触发点击事件的代码,我们在inner上添加点击事件,在wrapper添加事件,点击inner都会触发click事件。但这种情况需要我们每次都去点击回调函数才会执行,有没有函数不需要我们手动触发,自动触发呢?

<div class="wrapper">
  wrapper
  <div class="inner">inner</div>
</div>
<script>
  var $inner = document.getElementById('inner');
  var $wrapper = document.getElementById('wrapper');

  $inner.addEventListener('click', function () {
    console.log(this.innerHTML);
  });

  $wrapper.addEventListener('click', function () {
    console.log(this.innerHTML);
  });
  </script>
复制代码

这里用到了一个需要用到一个API:createEvent,具体代码如下:

let event = document.createEvent('Event')
  event.initEvent('click', true, true)
  $inner.dispatchEvent(event)
复制代码

这里我们通过createEvent创建了一个事件,并且其后必须马上进行初始化,然后通过dispatchEvent进行事件分发,这样就用js代码进行事件的触发,而不需要我们进行点击才能触发。

事件模拟

在event模块中有这么一段代码

focus = {focus: 'focusin', blur: 'focusout'},
hover = {mouseenter: 'mouseover', mouseleave: 'mouseout'}
复制代码

focus和blur我们都知道,但是为什么要重新隐射focusin和blur事件呢,在mdn中我们可以看到focus和focusin的区别在于focus不支持事件冒泡,如果不支持事件冒泡,那么带来的效果就是不能够进行事件委托。

同样的mouseenter和mouseleave也不支持事件冒泡,但是mouseenter会带来巨大的性能消耗,所以我们常用mouseover进行mouseenter进行事件的模拟。在鼠标事件中,有一个relatedTarget事件,在前面提到因为mouseover支持冒泡,那该如何来模拟mouseenter事件呢。relatedTarget事件属性返回的是和事件的目标节点相关的节点。对于mouseover事件来说,该属性是鼠标指针移到目标节点上所离开的那个节点。对于mouseout事件来说,该属性是离开目标时,鼠标进入的节点。根据上面的描述,我们可以对relatedTarget的值进行判断:如果值不是目标元素,也不是目标元素的子元素,就说明鼠标已经移入目标元素而不是在元素内部移动

核心代码

zid

var _zid = 1
function zid(element) {
  return element._zid || (element._zid = zid++)
}
复制代码

zid主要是用来标记已经绑定时间的元素,这个函数返回元素的_zid,如果没有,那就全局的zid加一,并且赋值给元素的_zid属性

parse

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

parse方法用来分解事件名和命名空间,{e: 事件名, ns: 命名空间},先把event变成字符串进行分割,得到事件名,和命名空间,命名空间可以为s1.s2.s3这种

compatible

这是用来修正event对象中浏览器的差异

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

    event.timeStamp || (event.timeStamp = Date.now())

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

具体来看看他的代码

if (source || !event.isDefaultPrevented) {
    source || (source = event)
复制代码

如果原事件存在,或者事件event的isDefaultPrevented为false或者不存在成立 如果原事件source不存在,就把event赋值给source

$.each(eventMethod, function(name, predicate) {
    var sourceMethod = source[name]
    event[name] = function(){
      this[predicate] = returnTrue
      return sourceMethod && sourceMethod.apply(source, arguments)
    }
  }) 
复制代码

这里是遍历eventMethod,获取原事件对应的方法名sourceMethod。对event事件进行重新赋值,先把方法赋值为returnTrue函数,返回执行原方法的返回值。

event[predicate] = returnFalse
复制代码

新添加的属性初始化为returnFalse。

event.timeStamp || (event.timeStamp = Date.now())
复制代码

看事件是否支持timeStamp,如果不支持,将Date.now()赋值给timeStamp,最后返回做了兼容性处理的event。

createProxy

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)
}
复制代码

这个函数的作用在于生成代理的event,首先在proxy的originalEvent挂载本身,然后遍历event,将event的属性复制到proxy,最后返回对proxy和event做兼容性处理。

add

// element 事件绑定的元素,events绑定的事件列表,fn事件执行时的句柄,data传递给事件对象的数据
// 绑定元素的选择器,delegator事件委托函数,capture哪个阶段执行事件句柄
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方法主要是给元素添加事件和事件响应。

id = zid(element), set = (handlers[id] || (handlers[id] = []))
复制代码

获取element的id,然后通过id来获取他的句柄容器

events.split(/\s/).forEach(function (event) {
  if (event == 'ready') return $(document).ready(fn)
})
复制代码

对events进行分解,如果event是ready就直接执行fn

var handler   = parse(event)
    handler.fn    = fn
    handler.sel   = selector
复制代码

对event进行事件名和命名空间进行分离,然后将信息挂载到handler上,handler的最终结构是这样的:

{
  fn: '', // 函数
  e: '', // 事件名
  ns: '', // 命名空间
  sel: '', // 选择器
  i: '',  // 函数索引
  del: '', // 委托函数
  proxy: '' // 代理函数
}
复制代码

继续看下面的

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)
     }
   }
 }
复制代码

这就是我们最先提到的mouseover和mouseenter事件,这里就是对Hover事件进行判断,如果related不存在,或者related不等于目标元素,并且不是目标元素的子元素,就能够完成mouseenter的模拟,然后返回函数处理后的结果。

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进行兼容处理,然后判断是否阻止默认行为,如果是则啥都不做,把data挂载到event对象上,e是事件执行时的event对象,并且使用compatible对e进行修正。调用句柄,并且返回处理结果。

set.push(handler)
if ('addEventListener' in element) 
  element.addEventListener(realEvent(hander.e), handler.proxy, eventCapture(handler, capture))
复制代码

向句柄容器添加句柄,并且给元素添加事件。

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
 }
复制代码

如果event不是字符串,可能就是对象或者数组,然后对其遍历,每个都调用on函数。

if (!isString(selector) && !isFunction(callback) && callback !== false)
      callback = data, data = selector, selector = undefined
    if (callback === undefined || data === false)
      callback = data, data = undefined
复制代码

这是针对参数不同的情况进行的操作

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

如果one为true,autoRemove进行的操作是把元素上对应的事件进行解绑,并且调用回调。

if (selector) 
  delegator = function (e) {
    var evt, match = $(e.target).closet(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)
复制代码

如果selector,就需要做事件代理,调用closet找到最近selector的元素。如果match存在,并且不是当前元素,就调用createProxy(),给当前事件对象创建代理对象,在调用extend方法,为其扩展currentTarget和liveFired属性,将代理元素和触发元素保存到事件对象中。 最后执行句柄函数,match作为上下文,用代理后的event对象evt替换掉原句柄函数的第一个函数。

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;
}
复制代码

triggerHandler用于直接执行函数。

this.each(function(i, element) {
    e = createProxy(isString(event) ? $.Event(event) : event)
    e._args = args
    e.target = element
复制代码

遍历元素,然后判断event如果是字符串则使用$.Event创建事件,然后使用createProxy创建代理。

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

寻找元素上所有的句柄,handler.proxy我们在之前提到过这是真的回调函数,如果有isImmediatePropagationStopped,则终止遍历。

Event

$.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)
}
复制代码

简单来说这个部分就是创建事件,初始化事件,然后返回兼容后的event。


以上所述就是小编给大家介绍的《Zepto源码学习Event模块》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Sprint

Sprint

Jake Knapp、John Zeratsky、Braden Kowitz / Simon & Schuster / 2016-3-8 / GBP 14.60

媒体推荐 “Every business leader I know worries about the same thing: Are we moving fast enough? The genius of Jake Knapp’s Sprint is its step-by-step breakdown of what it takes to solve big problems an......一起来看看 《Sprint》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具