博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Zepto源码学习Event模块
阅读量:6694 次
发布时间:2019-06-25

本文共 9828 字,大约阅读时间需要 32 分钟。

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

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

了解基本event信息

事件分发

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

wrapper
inner
复制代码

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

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

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

事件模拟

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

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

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

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

核心代码

zid

var _zid = 1function 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。

参考文章:

转载地址:http://rhcoo.baihongyu.com/

你可能感兴趣的文章
产品架构开发方法 分享记录
查看>>
Windows Azure Cloud Service (40) 使用VS2013的publishSettings文件,发布Cloud Service
查看>>
异常处理原则
查看>>
Visual SVN 2.0.1下载+破解
查看>>
ASP.NET服务器推送及前后台实时交互
查看>>
sql server游标
查看>>
UML设计初步 - 基本概念一(actor, use case)
查看>>
关于Python中的for循环控制语句
查看>>
《http权威指南》阅读笔记(二)
查看>>
Javascript特效代码大全(420个)(转)
查看>>
jQuery闭包之浅见,从面向对象角度来理解
查看>>
(原创)北美信用卡(Credit Card)个人使用心得与总结(个人理财版) [精华]
查看>>
gevent
查看>>
LightOJ 1018 Brush (IV)(记忆化搜索)
查看>>
x264编码参数大测试:03 subme与crf(c)
查看>>
对自然数的有限区间散列
查看>>
低端路由器和高端路由的区别
查看>>
android webview 播放swf 失败<彻底解决黑框>
查看>>
应用程序实例——用户信息管理
查看>>
中文分词 mmseg4j 在 lucene 中的使用示例
查看>>