zepto源码之event.js

xiaoxiao2021-02-27  200

一、前言

event.js主要用于提供注册自定义事件和手动触发事件等功能。

二、源码

//event事件(事件处理) (function ($) { var _zid = 1, undefined, slice = Array.prototype.slice, isFunction = $.isFunction, isString = function (obj) { return typeof obj == 'string' }, handlers = {},//_zid: events 事件缓存池 specialEvents = {}, //是否支持即将获取焦点时触发函数 onfocusin 事件类似于 onfocus 事件。 主要的区别是 onfocus 事件不支持冒泡。 focusinSupported = 'onfocusin' in window, focus = {focus: 'focusin', blur: 'focusout'}, //mouseenter、mouseleave不冒泡 hover = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; //此处标准浏览器,click、mousedown、mouseup、mousemove抛出的就是MouseEvents,应该也是对低版本IE等某些浏览器的修正 specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'; //取元素标识符,没有设置一个返回 function zid(element) { return element._zid || (element._zid = _zid++) } //根据形参条件 查找元素上事件响应函数集合 function findHandlers(element, event, fn, selector) { event = parse(event); if (event.ns) var matcher = matcherFor(event.ns); /** * 若有event.e ,则判断事件类型是否相同,否则直接走下一步 * 若有event.e,则判断事件命名空间是否相同 RegExp.prototype.test = function(String) {}; * zid(handler.fn)返回handler.fn的标识,没有加一个,判断fn标识符是否相同 * 若有selector 则判断selector是否相同 * */ 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) }) } /** * 解析事件类型 parse("click.zhutao.xiaoyu"); => Object {e: "click", ns: "xiaoyu zhutao"} * slice取从索引为1之后的所有项,sort对数组进行排序,join(" ")将数组变为字符串,中间插入空格 * */ function parse(event) { var parts = ('' + event).split('.'); return {e: parts[0], ns: parts.slice(1).sort().join(' ')} } //生成命名空间的正则对象 matcherFor("xiaoyu zhutao"); => /(?:^| )xiaoyu.* ?zhutao(?: |$)/ function matcherFor(ns) { return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') } //addEventListener 的第三个参数,true - 事件句柄在捕获阶段执行,false - 默认,事件句柄在冒泡阶段执行 function eventCapture(handler, captureSetting) { return handler.del && (!focusinSupported && (handler.e in focus)) || !!captureSetting } //修正事件类型 focus->focusIn blur->focusOut mouseenter->mouseover mouseleave->mouseout function realEvent(type) { return hover[type] || (focusinSupported && focus[type]) || type } //增加事件底层方法; add(element, event, callback, data, selector, delegator || autoRemove) 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; //如果事件是mouseenter, mouseleave,模拟mouseover mouseout事件处理 if (handler.e in hover) fn = function (e) { /** * relatedTarget 事件属性返回与事件的目标节点相关的节点。 * 对于 mouseover 事件来说,该属性是鼠标指针移到目标节点上时所离开的那个节点。 * 对于 mouseout 事件来说,该属性是离开目标时,鼠标指针进入的节点。 * 对于其他类型的事件来说,这个属性没有用。 * */ var related = e.relatedTarget; //当related不在事件对象event内 表示事件已触发完成,不是在move过程中,需要执行响应函数 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); //如果某个监听函数执行了event.stopImmediatePropagation()方法,则除了该事件的冒泡行为被阻止之外(event.stopPropagation方法的作用),该元素绑定的后序事件的监听函数的执行也将被阻止. if (e.isImmediatePropagationStopped()) return; e.data = data; //执行回调函数,context:element,arguments:event,e._args(默认是undefind,trigger()时传递的参数) var result = callback.apply(element, e._args == undefined ? [e] : [e].concat(e._args)); //当事件响应函数返回false时,阻止浏览器默认操作和冒泡 if (result === false) e.preventDefault(), e.stopPropagation(); return result }; //设置事件响应函数的索引,删除事件时,根据它来删除 delete handlers[id][handler.i] handler.i = set.length; set.push(handler); if ('addEventListener' in element) element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) } //删除事件,对应add 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删除掉数组中的元素后,会把该下标出的值置为undefined,数组的长度不会变 delete handlers[id][handler.i] if ('removeEventListener' in element) element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) }) }) } //此处不清楚要干嘛,将事件两个核心底层方法封装到event对象里,方便做Zepto插件事件扩展吧 $.event = {add: add, remove: remove}; //接受一个函数,然后返回一个新函数,并且这个新函数始终保持了特定的上下文(context)语境,新函数中this指向context参数 $.proxy = function (fn, context) { //如果传了第3个参数,取到第3个参数以后(包含第3个参数)所有的参数数组 var args = (2 in arguments) && slice.call(arguments, 2); if (isFunction(fn)) { //args.concat(slice.call(arguments))将代理函数的参数与$.proxy的第三个及后面可选参数合并 var proxyFn = function () { return fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments) }; //标记函数 proxyFn._zid = zid(fn); return proxyFn } else if (isString(context)) { //另外一种形式,原始的function是从上下文(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") } }; $.fn.bind = function (event, data, callback) { return this.on(event, data, callback) }; $.fn.unbind = function (event, callback) { return this.off(event, callback) }; //添加一个处理事件到元素,当第一次执行事件以后,该事件将自动解除绑定,保证处理函数在每个元素上最多执行一次。 $.fn.one = function (event, selector, data, callback) { return this.on(event, selector, data, callback, 1) }; var returnTrue = function () { return true }, returnFalse = function () { return false }, ignoreProperties = /^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/, eventMethods = { preventDefault: 'isDefaultPrevented', stopImmediatePropagation: 'isImmediatePropagationStopped', stopPropagation: 'isPropagationStopped' }; /** * 修正event对象 * @param event 代理的event对象|原生event对象 * @param source 原生event对象 * @returns {*} */ function compatible(event, source) { if (source || !event.isDefaultPrevented) { source || (source = event); /** * 遍历,给事件添加isDefaultPrevented、isImmediatePropagationStopped、isPropagationStopped方法 * isDefaultPrevented:如果preventDefault()被该事件的实例调用,那么返回true。这可作为跨平台的替代原生的defaultPrevented属性,如果defaultPrevented缺失或在某些浏览器下不可靠的时候。 * isImmediatePropagationStopped:如果stopImmediatePropagation()被该事件的实例调用,那么返回true。Zepto在不支持该原生方法的浏览器中实现它,(例如老版本的Android)。 * isPropagationStopped:如果stopPropagation()被该事件的实例调用,那么返回true。 **/ $.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()); //如果浏览器支持defaultPrevented DOM3 EVENT提出的能否取消默认行为 //source.defaultPrevented:判断默认事件是否已被阻止,与preventDefault()相对应,这是对各种情况的兼容 if (source.defaultPrevented !== undefined ? source.defaultPrevented : 'returnValue' in source ? source.returnValue === false : source.getPreventDefault && source.getPreventDefault()) event.isDefaultPrevented = returnTrue } //返回修正对象 return event } //新建一个对象 封装event,创建代理对象 function createProxy(event) { var key, proxy = {originalEvent: event}; //复制event属性至proxy,ignoreProperties里包含的属性除外 for (key in event) if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key]; return compatible(proxy, event) } $.fn.delegate = function (selector, event, callback) { return this.on(event, selector, callback) }; $.fn.undelegate = function (selector, event, callback) { return this.off(event, selector, callback) }; //冒泡到document.body绑定事件 $.fn.live = function (event, callback) { $(document.body).delegate(this.selector, event, callback); return this }; //在doument.body解绑事件 $.fn.die = function (event, callback) { $(document.body).undelegate(this.selector, event, callback); return this }; /** * 多个事件可以通过空格的字符串方式添加,或者以事件类型为键、以函数为值的对象方式。 * 如果给定css选择器,当事件在匹配该选择器的元素上发起时,事件才会被触发 * 如果给定data参数,这个值将在事件处理程序执行期间被作为有用的 event.data 属性 * */ $.fn.on = function (event, selector, data, callback, one) { var autoRemove, delegator, $this = this; //event是对象{ type: handler, type2: handler2, ... } 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; //如果false在回调函数的位置上作为参数传递给这个方法,它相当于传递一个函数,这个函数直接返回false。 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) { //closest 从元素本身开始,逐级向上级元素匹配,并返回最先匹配selector的元素。如果给定context节点参数,那么只匹配该节点的后代元素。 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) }) }; $.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) }) }; //在对象集合的元素上触发指定的事件。事件可以是一个字符串类型,也可以是一个 通过$.Event 定义的事件对象。如果给定args参数,它会作为参数传递给事件函数。 $.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); //可能不是dom元素上触发指定事件 else $(this).triggerHandler(event, args) }) }; // triggers event handlers on current element just as if an event occurred, // doesn't trigger an actual event, doesn't bubble $.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) { //调用 handler.proxy执行事件 result = handler.proxy(e); //如果event调用了immediatePropagationStopped(),终止后续事件的响应 if (e.isImmediatePropagationStopped()) return false }) }); return result }; //给常用事件生成便捷方法 ('focusin focusout focus blur load resize scroll unload click dblclick ' + 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave ' + 'change select keydown keypress keyup error').split(' ').forEach(function (event) { //有callback回调,是绑定事件,否则,触发事件 $.fn[event] = function (callback) { return (0 in arguments) ? this.bind(event, callback) : this.trigger(event) } }); //创建Event对象 $.Event = function (type, props) { //当type是个对象时 保证type为对象的属性字符串,props为对象 if (!isString(type)) props = type, type = props.type; //创建自定义事件,如果是click,mousedown,mouseup mousemove创建为MouseEvent对象,bubbles设为冒泡 var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true; //bubbles = !!props[name]冒泡判断;event[name] = props[name] props属性扩展到event对象上 if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) //初始化event对象,type为事件名称,如click,bubbles为是否冒泡,第三个参数表示是否可以用preventDefault方法来取消默认操作 event.initEvent(type, bubbles, true); return compatible(event) }; })(Zepto);

三、源码分析

$.Event

注册自定义事件核心流程: 1、document.createEvent(type) 创建一个新的自定义事件 2、event.initEvent(type, bubbles, cancelable) 初始化自定义事件 3、compatible(event) 给事件添加isDefaultPrevented、isImmediatePropagationStopped、isPropagationStopped方法

$.fn.on

on方法主要调用原生的target.addEventListener(type, listener[, useCapture]);方法。 1、$.fn.one方法是在on方法的回调函数中调用remove方法移除事件监听 2、如果on方法中有选择器,虽然是必须在子元素上点击才有效果,但是事件监听其实还是在父元素上,只不过在回调函数中设置了如果不是匹配的子元素触发不进行操作

$.fn.off

off方法主要调用原生target.removeEventListener(type, listener[, useCapture]);方法,与$.fn.on相对

$.fn.trigger

trigger方法判断元素如果是dom元素触发的,则调用原生target.dispatchEvent(event)方法;如果不是dom元素上触发的,那么直接手动调用回调函数。

handlers

事件缓存池,用于移除或者触发监听函数找到相应的回调函数,然后进行相应操作。

转载请注明原文地址: https://www.6miu.com/read-10882.html

最新回复(0)