从zepto.js了解如何手动(触发)触发DOM事件VirtualDOM可能会被鄙视。热情的背后往往是无尽的浮躁。学习这些先进、流行的类库或框架,可以让我们走得更快,但静下心来,返璞归真,打好基础,却能让我们走得更稳,走得更远。最近一直在看zepto的源码,希望通过学习掌握一些框架设计的技巧,重温巩固好久没捡起来的js基础。如果您对该系列感兴趣,欢迎点击下方观看地址,随时关注动态。本文主要想说说zepto中事件模块(event.js)的触发实现原理。原文地址仓库地址event.js模块zepto是由很多小模块组成的,基本的zepto.js模块,event.js事件处理模块,ajax.js请求处理模块等等。其中,核心的event.js事件处理模块完成了zepto中的事件绑定on,移除off以及手动触发trigger等功能。下面简单回顾一下如何使用zepto的这三个功能。12let$list=$('.list')letcb1=function(e,name){控制台.log(1,name)}letcb2=function(e,name){console.log(2,name)}$list.on('click',cb1)$list.on('click',cb2)//移除事件//我们可以指定一个事件处理器来移除点击事件$list.off('click',cb1)//也可以直接移除点击事件$list.off('click')//手动触发event$list.trigger('click','qianlongo')哥们,你开玩笑吧,jQuery、zepto这么眼熟,谁不会用这个!不客气,今天我们主要看一下它的源码是如何实现的。一步步看trigger是怎么实现的直接上传代码$.fn.trigger=function(event,args){//处理传入的事件,如果是字符串或者纯对象,获取一个自创事件object//如果传入的对象已经被$.Event处理过,那么就把它放到compatible里再改造一下(其实就是加几个方法,重写几个方法)event=(isString(event)||$.isPlainObject(事件))?$.Event(event):compatible(event)//传递给事件处理程序的args参数event._args=argsreturnthis.each(function(){//通过直接调用处理focus(),blur()if(event.typeinfocus&&typeofthis[event.type]=="function")this[event.type]()//集合中的项目可能不是DOM元素//触发dom事件elseif('dispatchEvent'inthis)this.dispatchEvent(event)//因为zepto对象里面的元素不一定是dom元素,所以此时直接触发回调函数else$(this).triggerHandler(event,args)})}直接贴了触发函数的代码可能会让我们感到困惑。什么是!!!$.fn是什么!!!什么是$.isPlainObject!!!$.Event是什么鬼?就好像有一连串的问题等着我们去解决。为了直接切入,不易理解,先来看看zepto中如何给基础的zepto.js模块添加函数先看zepto.js模块varzepto=(function(){//xxxxvar$=function(selector,context){returnzepto.init(selector,context)}return$zepto.Z.prototype=Z.prototype=$.fn//xxxx})()window.Zepto=Zeptowindow.$===复制代码undefined&&(window.$=Zepto)尝试删除一些不需要的代码,可以看出我们平时使用的Zepto其实是其匿名函数自执行派生出来的函数。而$.fn就是它的原型是如何给zepto.js模块添加功能的。zepto.js模块只有一些基本功能。例如,没有向dom添加事件的功能。如何添加?(function($){//xxx$.fn.on=function(){//xxxx}$.fn.off=function(){//xxxx}$.fn.trigger=function(){//xxxx}$.Event=function(){//xxx}//xxx})(Zepto)最后,减少了其他干扰代码。可以看到,所谓在zepto.js模块中添加函数,基本上就是在其原型中添加新的方法或者直接在$函数上设置一些静态方法。好了,我们已经解决了$,$.fn是什么的问题,现在回头开始一步步讲解如何手动触发事件。查看触发函数的源码$.fn.trigger=function(event,args){//处理传入的事件,如果是字符串或者纯对象,获取一个自创建的事件对象//如果传入event已经是$.Event处理过的对象,然后放入compatible再改造(其实就是加了几个方法,重写了几个方法)event=(isString(event)||$.isPlainObject(事件))?$.Event(event):compatible(event)//args是传递给事件处理程序的参数event._args=args//xxx}先删除下面的一些代码,我们先来了解一下这些代码。其中一个非常重要的函数是$.Event。至于isString=>判断是否为字符串isPlainObject=>判断是否为纯对象(必须是对象,window对象除外,对象的原型必须和Object的原型一致)兼容=>其实就是对事件对象event做一些扩展,比如增加一些方法,重写一些方法等等。这些方法暂时不需要太多关注。主要看$.Event,里面几乎包含了手动触发dom事件的大部分步骤和内容。我们主要看$.Event,里面几乎包含了如何手动触发一个dom事件的大部分步骤和内容。我们主要看$.Event,里面几乎包含了如何手动触发一个dom事件的大部分步骤和内容。$.Event是创建并初始化一个指定的dom事件对象,如果给定props,则扩展为事件对象$.Event=function(type,props){//当type是对象时,如{type:'click',data:'qianlongo'}if(!isString(type))props=type,type=props.type//click,mousedown,mouseupmousemove对应MouseEvent//其他事件对应Events//并放Bubbles设置为true表示事件冒泡,false表示不冒泡varevent=document.createEvent(specialEvents[type]||'Events'),bubbles=true//当props存在时,循环遍历props,展开其属性为事件对象if(props)for(varnameinprops)(name=='bubbles')?(bubbles=!!props[name]):(event[name]=props[name])//初始化事件对象,第一个是事件类型,第二个是冒泡与否,第三个是是否可以通过preventDefault来阻止浏览器的默认行为event.initEvent(type,bubbles,true)//ThenProcessthecreatedtimeobjectandreturnreturncompatible(event)}注解已经写的很清楚了,这个函数就是返回aninitializedeventobject这里直接总结一下手动触发dom事件的基本步骤手动触发dom事件需要3个步骤。如果你对document.createEvent不是很熟悉,可以点击查看。创建事件对象document.createEvent(event)初始化事件对象event.initEvent(type,bubbles,true)分发事件dom.dispatchEvent(event)到这里前两步已经完成,剩下最后一步了。我们看一下trigger剩下的代码是手动触发dom事件的。最后一步$.fn.trigger=function(event,args){//处理传入的事件,如果是字符串或者纯对象,获取一个自创建的事件对象//如果传入的对象已经被处理通过$.Event,把它变成compatible再改造一下(其实就是加了几个方法,重写了几个方法)event=(isString(event)||$.isPlainObject(event))?$.Event(event):compatible(event)//传递给事件处理程序的args参数event._args=argsreturnthis.each(function(){//通过直接调用处理focus(),blur()if(event.typeinfocus&&typeofthis[event.type]=="function")this[event.type]()//集合中的项目可能不是DOM元素//触发dom事件elseif('dispatchEvent'inthis)this.dispatchEvent(event)//因为zepto对象里面的元素不一定是dom元素,所以此时直接触发回调函数else$(this).triggerHandler(event,args)})}最后一步其实就是对当前选中的元素进行每次遍历,然后判断要触发的事件是focus还是blur,如果是则直接执行。更进一步,如果当前dom元素属性中存在dispatchEvent方法,那么该事件就会被触发。(为什么要这一步?因为我们知道$()函数可以有多种使用方式,而某些方式获取的zepto对象并没有选择dom节点。)最后还有一个else分支,不会触发手动事件。而是直接触发注册事件时添加的事件处理器(因为这里涉及到zepto事件模块中如何管理元素和事件队列的映射关系,篇幅会比较长,下篇文章会提到,这里就不解释了)手动DIY一个根据上面的描述,手动触发DOM事件并没有那么神奇。完成三个步骤后,目标就可以实现了。让我们手动写一个例子123let$list=document.querySelector('.list')let$item1=document.querySelector('.item1')$list.addEventListener('click',function(){console.log(this.innerHTML)},false)$item1.addEventListener('click',function(){console.log(this.innerHTML)},false)//1.创建事件对象document.createEvent(event)letevent=document.createEvent('Event')//2.初始化事件对象event.initEvent(type,bubbles,true)event.initEvent('click',true,true)//3.Dispatcheventdom.dispatchEvent(event)$item1.dispatchEvent(event)control此时tableprints11231是由item1的事件处理函数打印出来的。后面的li部分是用list打印出来的。如果initEvent的第二个参数设置为false,则不允许冒泡,最后只会打印1。如果这部分对您有帮助,请点个star吧!???仓库地址