JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】
正文
作者打字速度实在不咋地,源码部分就用图片代替了,都是截图,本文讲解的Zepto版本是1.2.0,在该版本中的event模块与1.1.6基本一致。此文的fastclick理解上在看过博客园各个大神的文章后对我确实有很大的帮助,当然,我的某些观点可能不是很准确甚至有错误,欢迎讨论,白天基本在线。
zepto的event
可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑
源码暂且不表,github里还有中文网站都能下到最新版的zepto。整个event模块不长,274行,我们可以看到,整个event模块,事件绑定核心就是on和off,还有一个trigger用来触发,类观察者模式,可以先看看汤姆大叔的深入理解JavaScript系列(32):设计模式之观察者模式,其余皆为实现的处理函数。
首先来个demo:
$("#btn").on("click",function(event){
console.log(event);
})
一个简单的click事件监听示例。
根据event模块中对于事件的使用来看:


on为开始(add)

可以看到,绑定函数有五个参数:
- event:事件类型,可以通过空格的字符串方式添加("click mousedown"),或者事件类型为键,函数为值的方式({click:function(),mousedown:function()})。
- selector:事件委托的节点选择器,可不传
- data:事件处理程序中的event.data属性
- callback:事件处理程序的回调函数
- one:绑定事件后,只触发一次回调
根据参数,我们可以很轻易的将on分为几部分(上图所示):
- 递归序列,处理event为键值对的情况
- 简写方式,如果只是简单的事件和回调的话($("#btn").on("click",function(){})),one参数不参与简写形式,有单独的one()方法。
- 循环zepto对象,因为这里的$this是zepto.init之后生成的对象,这里对于one和selector进行了autoRemove(只触发一次回调),delegetor(事件代理),然后是add(事件注册)
autoRemove,如果one为true,也就是只想使用一次,那么使用remove,并通过apply,给callback设立event对象;
而delegator中,如果selector是绑定元素的子节点,zepto以event.target为目标元素,判断是否触发节点的父级和传入的selector一致,上下文是遍历之后的节点。然后创建一个该事件对象的副本(createProxy),返回compatible()函数处理的event,当然,最后都会通过add()来进行注册:

首先是zid,zepto里面有个handlers对象,用于存放处理过的事件对象,_zid初始值为1,每次会按照值存入handlers,并且修改event对象中的_zid,每次存入的是一次绑定的所有事件:

因为每次使用$()创建的zepto对象都是新的,用handlers建立队列才能更好的进行管理。
之后就是对于以空白字符形式(/\s/)进行分割的字符串的处理,内部创建了handle,注意其parse方法是内部方法,而不是Date.parse()。
前面也说过,冒泡事件会有副作用,mouseover和mouseout,如果只是简单的节点,没有问题,但有了子节点之后。原先监听父节点的事件,会在鼠标移过去时再次触发。这是由于监听的是整个父节点,而移动到子节点时,子节点并没有事件,所以向上冒泡所造成的bug,而在DOM3级中,新定义了两个不冒泡的事件:mouseenter,mouseleave,使用这两个事件,可以解决这一问题。而在zepto中,使用了relatedTarget属性,并使用contains判断触发的节点在不属于移出(mouseover),移入(mouseout)时,才执行回调。且对不支持mouseenter和mouseleave事件的情况进行了兼容。
然后就是调用addEventListener,开始监听,这里没有做IE的兼容。事件句柄随着handler插入handles中,为之后的remove做准备。这里的proxy是对于event的扩充,也是添加了当return false时,调用
preventDefault()和stopPropagation()。
off移除

可以看出来,和on是对应的写法,同样可以分三部分,只不过这里的功能是移除监听而已。
就直接到了remove,这里主要做的就是根据传入的event和selector,用findHandlers进行查找,然后删除handlers中对应的事件,同时调用removeEventListener来移除事件处理程序。
$.Event自定义事件

这里使用的是createEvent()和initEvent(),这里的事件类型如果不是specialEvents中定义的MouseEvents,就会变成默认Events(DOM3)。
看下自定义的事件对象:

可以发现,因为使用compatible()封装了一下event,所以会有zepto新增的属性,以及我们传入的props属性。
trigger触发
我们知道,DOM3级中提供的触发事件的api是dispatchEvent()(低版本IE中是fireEvent()),而zepto这里也是一样:

可以看出,对于参数event为对象时进行了处理,意味着可以直接使用trigger创建+触发,支持dispatchEvent的情况下,会直接触发,如果不是DOM节点,则使用triggerHandler()来触发。
triggerHandler触发

可以看出,如果节点在之前绑定了其他事件处理程序且使用过stopImmdiatePropagation(),则也不会再触发自定义事件。
demo:

$.proxy
这其实是个独立的函数,在add()函数中使用的proxy,是handler.proxy()函数,与这个无关。这个函数起到的作用很类似extend,只不过它扩展的是上下文(执行环境)。
我倒是觉得,最关键的就是
fn.apply(fn.apply(context, args ? args.concat(slice.call(arguments)) : arguments))
$.proxy.apply(null,args);
自模拟click事件
zepto实际是为了能在手机上轻量级的使用而创造的,为了使用上的不卡顿,手机上不能用click事件,有延时,原因不多说了,写个小demo,可以看看pc和手机的点击事件耗时区别:
touch和click demo

如果pc端,最好用chrome,没大考虑兼容。用开发者工具也能模拟touch事件。
所以zepto提供了touch模块,我先自己模拟了下tap事件,连着又自定义了一些事件:
touch和tap demo

我的思路是,touch事件触发的很快,那么就用touch事件来模拟click,以touchstart和touchend为开始和结束,只考虑了单指的情况,touch事件本身就有:
- touches:表示当前跟踪的触摸操作的touch对象的数组。
- targetTouches:特定于事件目标的touch对象的数组。
- changeTouches:表示自上次触摸以来发生了什么改变的touch对象的数组。
这三个数组里会包含下列属性: - clientX:触摸目标在视口中的x坐标。
- clientY:触摸目标在视口中的y坐标。
- identifier:标识触摸的唯一ID。
- pageX:触摸目标在页面中的x坐标。
- pageY:触摸目标在页面中的y坐标。
- screenX:触摸目标在屏幕中的x坐标。
- screenY:触摸目标在屏幕中的y坐标。
- target:触摸的DOM节点目标。
在touchstart中可以使用touches,而在touchend里则没有touches数组信息了,用changeTouches来获取触摸结束时的信息。我主要使用了pageX和pageY,判断在250ms之下的情况里,如果手指在一个点上(我设了14px大小的范围,)touchstart加touchend都触发了,则为一次tap事件,还是用了creatEvent,所以返回的是一个自定义的tap事件

我没有把touch的属性填入自定义的事件里,就一层,所以也没考虑冒不冒泡了,后面可以完善下,tap倒是还好,dbltap稍稍耗了点时间。
zepto的touch模块
整个touch模块也很简单,还是先从入口开始:

可以看到zepto新增了这些事件,并做了简写的处理,整个部分最重要的是给document绑定了touch,MSpointer,pointer,MSGestureEvent的触摸事件,不过上面的

来看,应该有setTimeout,下面的cancelAll()中也有使用clearTimeout。
先看touch事件,其他的反正是兼容。
zepto定义的局部变量touch中有四个值,x1,x2,y1,y2,应该是用来记录第一个触发的点和第二个触发的点,果然在监听touchmove事件的回调里,使用了两个点来计算偏移,不过这里是将途中所有偏移量都与初始值进行比较,然后汇总。
在touchend中,对于swipe(滑动)和tap(点击)进行了处理,因为deltaX和deltaY需要在30*30的范围内才会被触发,但是它的偏移量是move移动的总和,所以在触发时容错率低,也就是不好点出来,相比较其他操作而言。
在touchcancel的处理中,清除所有延迟操作。
同时其整个操作其实是绑定在了document上,所以使用时如果有其他的touch事件也绑定在了doucment上,并且取消了冒泡事件,则之后的所有操作都会失效。
touch的点透
专门写了下demo来测试点透问题,点透事件的发生。这里也是之前的click300ms的延时带来的问题,如果最上层始终存在还好,就怕是点击消失的情况,那么如果上层用的是touch事件,下层是a标签,input或者绑定了click事件的节点,则也会被触发,只能说zepto的touch事件还需要我们自己来扩充和完善。
在通过对javaScript事件的详细学习之后,还是有很多途径去解决这个问题的,比如:
- preventDefault(),来取消touchstart和touchend的默认事件。
- 给两层之间加透明的中间层,用于阻止300ms之后的click传递到下层中。
- pointer-events:none;该css3属性可以取消节点上的所有click事件。可惜浏览器支持不良好

- fastclick,老早就想读下fastclick如何实现的,正好阅读一下。
fastclick
使用十分简单,只要FastClick.attach(document.body);就好,在github上也介绍了另两种方法,可以不使用fastClick来快速点击。

具体的可以直接去它的github地址看https://github.com/ftlabs/fastclick。
我修改了下之前的例子,新增了一个点击事件的例子,可以看出,基本比touchend慢1ms。而且使用了click事件,所以也不存在点透事件。
同样从入口进入,在代码内部,先实例化FastClick(),它有两个参数:layer和options,layer也就是我们之前传的document.body,
在FastClick()函数中,可以先找到

相当于document.body上的节点的click,touch事件的监听都会被FastClick内部的事件给处理掉,如果layer上有onclick事件,同样会被oldOnClick复写。
在pc上不用多说,绑定操作的节点肯定先通过对click的监听来处理,

这里的trackingClick应该是如果之前页面中的touchend被UI事件阻塞(可以简单模拟下,也就是出现字符被选中的情况),重新置空它。
而在手机上,最先触发的操作必定是touchstart和touchmove,touchend。

这里的targetElement很重要,其实是上面提过的event的target,实际触摸的节点目标,这个属性会在接下来的move,end中用来判断是否中间换过节点,并在sendClick中用来真实的触发事件

返回touchstart中,兼容先不看,这里对于初始节点也做了存储,touchStartX,touchStartY。
而接下来的对于double-tap的处理,其实需要看需求,如果是默认值,那么200ms内的第二次点击会禁止触发其默认事件event.preventDefault(),在手机上就无法触发双击事件。
然后就是touchmove,这里倒是很短,只是对于targetElement做了个判断,是否是同一个触摸节点。

在touchend中,同样略过兼容不看,不过我倒是又学到了一个原生api,document.elementFromPoint,传入x,y来找到节点,只是认识了一下,实不实用不表。

其实一共有三种方式来操作fastclick内部自定义的事件,不过当targetTagName=label时,这里focus了一下,把这里的触发放到了needsClick中而已,而在这两块中,可以知道,如果给className中加入needsFocus和needsClick,则会中断fastclick,使用原生事件:
needsFocus:第一个判断事件从touchstart到touchend是否超过100ms,则将targetElement置空,其实就是回归原来的方法,取消fastclick的事件。然后就是focus事件

这里使用了setSelectionRange(length,length),先选中文本。

有兴趣的可以去MDN看,不过我测试了一下,上面的例子中有个input,其中确实会有http://www.cnblogs.com/vajoy/p/5522114.html#!/follow提到的问题,在快速点击时光标会直接移到最后面,而上面传入的参数都是length,自然就跑到了最后面。
再通过sendClick来触发,就会有问题,而且在触发完自定义click之后,

取消了我们点击时带的focus操作,这里我先注释了一下代码,果然,光标先定到末尾,再定到正确位置,虽然上述博主的方法暂缓此问题,但在有些浏览器中仍然存在这一问题,并且实际上仍然是光标定到末尾,再定到正确位置,不如直接不用模拟的focus,作者自己的注释里说的是ios7中有问题,但focus中用的是deviceISIOS,而不是deviceIsIOSWithBadTarget,改这里也能解决问题。needsClick:这里首先preventDefault,再使用sendClick,上面稍微介绍了下,这里模拟的都是MouseEvents事件,使用targetElement来触发。
那么为何fastclick不穿透呢,我自然还是找代码中哪里用了preventDefault和stopImmediatePropagation,在onMouse中,还是防止快速点击而加的阻止操作,实际上阻止点透的仍然是needsClick中的event.preventDefault(),去掉之后,页面点击仍然会点透。
比较
知道了还是preventDefault()起了作用,我们再回头看touch模块,对比一下fastclick,其实两者的主体层都是差不多的,document和document.body实际在大多数情况下,可点击域都是一样,也就是说两者都绑定到了最外层上(近似),但fastclick提供了layer和options,意味着可以规避风险,而zepto的touch模块则直接绑定到了document之上,至少在使用上,并没有fastclick方便,不过其定义的各种touch动作,很有意义,在上面说过,在zepto解决点透,可以:
$("#btn").tap(function(){
// do something
}).on('touchend',function(e){
e.preventDefault();
});
在读源码时,layer绑定的部分,上面有图,确实会误导。因为实际上onClick是可以不走的,之所以在手机上触发的仍然是click事件,是因为在sendClick里直接使用dispatchEvent()触发了click,所以才会从fastclick定义的this.onClick()中走,其实这一部分可以和zepto一样放入touchend模块里面。
这么看,两个代码的核心其实大致相同,所不同的是fastclick中还加入的tap事件的focus方法,如果在zepto的touch中直接加入preventDefault(),则input无法获得焦点,所以可以引入focus事件,从而解决问题


可能会有兼容性的问题,对于blur()和focus()写完jQuery之后可以加入兼容性的写法。
现在就不会点透了。
还可以把touchstart,touchend操作放到具体的节点上,然后在节点中进行处理,不过这样会改动的比较大。zepto中有个方法$.proxy,放在这里有奇效,上面介绍过。
其他
至于说delegate,undelegate,live,die这些代理事件,还有bind,unbind等绑定操作,其实都是on在起作用,不细说。中间去看了fastclick,以及其他事情,其实星期一就写完了,还是基础薄弱啊。
之后是看看jQuery的事件操作,压力山大。

JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】的更多相关文章
- JavaScript事件详解-zepto的事件实现
zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们 ...
- JavaScript事件详解-jQuery的事件实现(三)
正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...
- javascript - 事件详解(阻止事件冒泡+阻止事件行为)
一.事件流 1.事件流 描述的是在页面中接受事件的顺序 2.事件冒泡 由最具体的元素接收,然后逐级向上传播至最不具体的元素的节点 (最具体 –> 最不具体) 3.事件捕获 最不具体的节点先接收事 ...
- Android Launcher拖拽事件详解【android4.0--Launcher系列二】
AndroidICS4.0版本的launcher拖 拽的流程,基本和2.3的相似.就是比2.3写的封装的接口多了一些,比如删除类的写法就多了个类.等等.4.0的改变有一些,但是不是特别大.这个月一 直 ...
- JavaScript中的鼠标滚轮事件详解
JavaScript中的鼠标滚轮事件详解/*Firefox注册事件*/ ~~~Firefox: addEventListener('DOMMouseScroll', handler, false)if ...
- SQL Server 默认跟踪(Trace)捕获事件详解
SQL Server 默认跟踪 -- 捕获事件详解 哪些具体事件默认跟踪文件能够捕获到? --returns full list of events SELECT * FROM sys.trace_e ...
- JAVASCRIPT事件详解-------原生事件基础....
javaScirpt事件详解-原生事件基础(一) 事件 JavaScript与HTML之间的交互是通过事件实现的.事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间,通过监听特定事件的发生,你能 ...
- 第三天:JS事件详解-事件流
学习来源: F:\新建文件夹 (2)\HTML5开发\HTML5开发\04.JavaScript基础\6.JavaScript事件详解 学习内容: 1)基础概念 2)举例说明: 代码如上,如果用事件 ...
- js中鼠标滚轮事件详解
js中鼠标滚轮事件详解 (以下内容部分内容参考了http://adomas.org/javascript-mouse-wheel/ ) 之前js 仿Photoshop鼠标滚轮控制输入框取值中已使用 ...
随机推荐
- WaitGroup is reused before previous Wait has returned
当你Add()之前,就Wait()了,就会发生这个错误.
- 物联网框架ServerSuperIO(SSIO)更新、以及增加宿主程序和配置工具,详细介绍
一.更新内容 1.修改*Server类,以及承继关系.2.增加IRunDevice的IServerProvider接口继承.3.修复增加COM设备驱动可能造成的异常.4.修复网络发送数据可能引发的异常 ...
- SYN Flood测试
由于工作需要对公司进行SYN Flood测试,在网上查了些资料,Youtube上找到最多的方法就是hping3工具来实现, 该工具已经预装在Kali下,具体操作用一条命令即可实现. hping3 -S ...
- Java中的经典算法之冒泡排序(Bubble Sort)
Java中的经典算法之冒泡排序(Bubble Sort) 神话丿小王子的博客主页 原理:比较两个相邻的元素,将值大的元素交换至右端. 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面.即在第一 ...
- Java 性能分析工具 , 第 1 部分: 操作系统工具
引言 性能分析的前提是将应用程序内部的运行状况以及应用运行环境的状况以一种可视化的方式更加直接的展现出来,如何来达到这种可视化的展示呢?我们需要配合使用操作系统中集成的程序监控工具和 Java 中内置 ...
- 如何定义好一个符合规范的url
描述 进公司没有多久遇到一个问题,定义的url会被大神吐槽说是很渣.之前从来没有注意这块,今天把我们团队的url规范分享给大家. 为什么需要URL规范化 1.网站URL和结构已经成为网站搜索引擎友好的 ...
- Scala:Java 项目中混入scala代码
Spark 是用Scala代码写的.为了调试Spark,做了如下尝试. 1.Eclipse下:Java 项目 ,Using Maven,编写了一个java 版Spark应用. Spark的代码(sca ...
- 基于easyUI实现组织结构树图形
一. 准备工作 1. 点击此下载相关文件 2. 进入 js 文件夹,解压缩 jquery-easyui-1.5.rar 到当前文件夹 二. 在浏览器中运行 organize.html 文件,即可看到效 ...
- js 常用的正则表达式
以下收录一些我经常用到的正则表达式,因为工作场景中用到正则的地方几乎都跟validate插件的验证有关系, 所以以下正则也是$.validator.addMethod() 的拓展: validate: ...
- 【C++】继承(虚基类)
类的继承与派生 面向对象技术强调软件的可重用性,这种重用性通过继承机制来实现.而在类的继承过程中,被重用的原有类称为基类,新创建的类称为派生类.派生类定义语法格式如下: class <派生类名& ...