前言
套用上篇文章向zepto.js学习如何手动触发DOM事件 的开头😀😀😀
前端在最近几年实在火爆异常,vue、react、angular各路框架层出不穷,咱们要是不知道个双向数据绑定,不晓得啥是虚拟DOM,也许就被鄙视了。火热的背后往往也是无尽的浮躁,学习这些先进流行的类库或者框架可以让我们走的更快,但是静下心来回归基础,把基石打牢固,却可以让我们走的更稳,更远。
最近一直在看zepto的源码,希望通过学习它掌握一些框架设计的技巧,也将很久不再拾起的js基础重新温习巩固一遍。如果你对这个系列感兴趣,欢迎点击watch
,随时关注动态。这篇文章主要想说一下zepto中事件模块(event.js)的添加事件on
以及移除事件off
实现原理,中间会详细地讲解涉及到的细节方面。
如果你想看event.js全文翻译版本,请点击这里查看
说在前面
在没有vue和react,甚至angular都没怎么接触的刀耕火种的时代,jQuery或者zepto是我们手中的利器,是刀刃,他让我们游刃有余地开发出兼容性好的漂亮的网页,我们膜拜并感叹作者带来的便利,沉浸其中,无法自拔。
但是用了这么久的zepto你知道这样写代码
|
|
是怎么实现事件委托的吗?为啥此时的this
就是你点中的li
呢?
平常我们可能还会这样写。
|
|
写法有点多,也许你还有其他的写法,那么
on
bind
delegate
live
click()
这些添加事件的形式,有什么区别,内部之间又有什么联系呢?
相信你在面试过程中也遇到过类似的问题(看完这边文章,你可以知道答案的噢😯
)?
接下来我们从源码的角度一步步去探究其内部实现的原理。
一切从on
开始
为什么选择从
on
添加事件的方式开始说起,原因在于其他写法几乎都是on
衍生出来的,明白了on
的实现原理,其他的也就差不多那么回事了。
祭出一张画了好久的图
上面大概是zepto中on
形式注册事件的大致流程,好啦开始看源码啦,首先是on函数,它主要做的事情是注册事件前的参数处理,真正添加事件是内部函数add。
|
|
直接看到这么一大坨的代码不易于理解,我们分段进行阅读。
第一段
|
|
这段代码主要是为了处理下面这种调用形式。
|
|
这种写法我们平时写的比较少一点,但是确实是支持的。而zepto的处理方式则是循环调用on
方法,以key
为事件名,val
为事件处理函数。
在开始第二段代码阅读前,我们先回顾一下,平时经常使用on
来注册事件的写法一般有哪些
|
|
还会有其他的写法,但是常见的可能就是这些,第二段代码就是处理这些参数以让后续的事件正确添加。
第二段
|
|
三个if语句很好的处理了多种使用情况的参数处理。也许直接看不能知晓到底是如何做到的,可以试试每种使用情况都代入其中,找寻其是如何兼容的。
接下来我们第三段
这段函数做了非常重要的两件事
- 处理one传入为true,事件只触发一次的场景
- 处理传入了selector,进行事件代理处理函数开发
我们一件件看它如何实现。
|
|
内部用了一个remove
函数,这里先不做解析,只要知道他就是移除事件的函数就可以,当移除事件的时候,再执行了传进来的回调函数。进而实现只调用一次的效果。
那么事件代理又是怎么实现咧?
回想一下平常自己是怎么写事件代理的,一般是利用事件冒泡(当然也可以使用事件捕获)的性质,将子元素的事件委托到祖先元素身上,不仅可以实现事件的动态性,还可以减少事件总数,提高性能。
举个例子
我们把原本要添加到li上的事件委托到父元素ul上。
|
|
|
|
回到第三段
|
|
zepto中实现事件代理的基本原理是:以当前目标元素e.target
为起点向上查找到最先符合selector
选择器规则的元素,然后扩展了事件对象,添加了一些属性,最后以找到的match元素作为回调函数的内部this
作用域,并将扩展的事件对象作为回调函数的第一个参数传进去执行。
这里需要知道.closest(...)
api的具体使用,如果你不太熟悉,请点击这里查看
说道这里,事件还没有添加啊!到底在哪里添加的呢,on函数的最后一句,便是要进入事件添加了。
|
|
参数处理完,开始真正的给元素添加事件了
zepto的内部真正给元素添加事件的地方在add函数。
|
|
我的神,又是这么长长长长的一大坨,人艰不拆,看着心累啊啊啊啊!!!
不过不用急,只要一步步去看,最终肯定可以看懂的。
开头有一句话
|
|
|
|
zepto中会给添加事件的元素身上加一个唯一的标志,_zid从1开始不断往上递增。后面的事件移除函数都是基于这个id来和元素建立关联的。
|
|
handlers
便是事件缓冲池,以数字0, 1, 2, 3…保存着一个个元素的事件处理程序。来看看handlers长啥样。
html
|
|
javascript
|
|
以上截图便是这段代码执行后得到的handlers,其本身是个对象,每个key(1, 2, 3 …)(这个key也是和元素身上的_zid属性一一对应的)都保存着一个数组,而数组中的每一项目都保存着一个与事件类型相关的对象。我们来看看,每个key的数组都长啥样
|
|
这样的设置给后面事件的移除带了很大的便利。画个简单的图,看看元素添加的事件和handlers中的映射关系。
明白了他们之间的映射关系,我们再回到源码处,继续看。
|
|
暂时去除了一些内部代码逻辑,我们看到其对event
做了切分,并循环添加事件,这也是我们像下面这样添加事件的原因
|
|
那么接下来我们要关注的就是循环的内部细节了。添加了部分注释
|
|
至此,添加事件到这里告一段落了。让我们再回到文章初始的问题,
on
bind
delegate
live
click()
这些添加事件的形式,有什么区别,内部之间又有什么联系呢?其实看他们的源码大概就知道区别
|
|
bind和click()函数都是直接将事件绑定到元素身上,live则代理到body元素身上,delegate是小范围是事件代理,性能在由于live,on就最厉害了,以上函数都可以用on实现调用。
事件移除的具体实现
事件移除的实现有赖于事件绑定的实现,绑定的时候,把真正注册的事件信息都和dom关联起来放在了handlers中,那么移除具体是如何实现的呢?我们一步步来看。
同样先放一张事件移除的大致流程图
off函数
|
|
off函数基本上和on函数是一个套路,先做一些基本的参数解析,然后把移除事件的具体工作交给remove函数实现,所以我们主要看remove函数。
remove函数
|
|
继续往下走,一个重要的函数findHandlers
|
|
因为注册事件的时候回调函数不是用户传入的fn,而是自定义之后的proxy函数,所以需要将用户此时传入的fn和handler中保存的fn相比较是否相等。
结尾
罗里吧嗦说了好多,不知道有没有把zepto中的事件处理部分说明白说详细,欢迎大家提意见。
如果对你有一点点帮助,点击这里,加一个小星星好不好呀
如果对你有一点点帮助,点击这里,加一个小星星好不好呀
如果对你有一点点帮助,点击这里,加一个小星星好不好呀