彻底弄懂jQuery事件原理二
上一篇说到,我们在最外层API的on,off,tiggler,triggerHandler调用的是event方法的add,remove和tirgger方法,本篇就来介绍event辅助类
\
先放个图,这其实就是整个事件流程的过程:

1. add方法:
add: function(elem, types, handler, data, selector) {
            var handleObjIn, eventHandle, tmp,
                events, t, handleObj,
                special, handlers, type, namespaces, origType,
                elemData = data_priv.get(elem);
            // Don't attach events to noData or text/comment nodes (but allow plain objects)
       //取出dom对象的数据缓存对象,如果为空或是text,comment节点,直接退出,不会绑定事件
            if (!elemData) {
                return;
            }
            // Caller can pass in an object of custom data in lieu of the handler
            if (handler.handler) {
                handleObjIn = handler;
                handler = handleObjIn.handler;
                selector = handleObjIn.selector;
            }
            // Make sure that the handler has a unique ID, used to find/remove it later
       //guid是否存在,不存在创建一个,用于标示和删除事件
            if (!handler.guid) {
                handler.guid = jQuery.guid++;
            }
            // Init the element's event structure and main handler, if this is the first
       //检测数据缓存中是否存在事件队列,没有创建一个,用于保存绑定的事件
            if (!(events = elemData.events)) {
                events = elemData.events = {};
            }
        //为数据缓存添加一个事件处理器,后面会用到
            if (!(eventHandle = elemData.handle)) {
                eventHandle = elemData.handle = function(e) {
                    // Discard the second event of a jQuery.event.trigger() and
                    // when an event is called after a page has unloaded
                    return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
                        jQuery.event.dispatch.apply(eventHandle.elem, arguments) :
                        undefined;
                };
                // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
         //定义事件处理器对应的元素,用于防止IE非原生事件中的内存泄露
                eventHandle.elem = elem;
            }
            // Handle multiple events separated by a space
            // jQuery(...).bind("mouseover mouseout", fn);
            // core_rnotwhite:/\S+/g
       //多个事件通过空格拆分
            types = (types || "").match(core_rnotwhite) || [""];
            // 例如:'.a .b .c'.match(/\S+/g) → [".a", ".b", ".c"]
            t = types.length;
        
       //对每个事件进行处理
            while (t--) {
                //命名空间处理 如"mouseover.a.b" → tmp = ["mouseover.a.b", "mouseover", "a.b"]
                tmp = rtypenamespace.exec(types[t]) || [];
                type = origType = tmp[1];
                namespaces = (tmp[2] || "").split(".").sort();//namespace = ['a','b']
                // There *must* be a type, no attaching namespace-only handlers
          //如果事件不存在,不处理
                if (!type) {
                    continue;
                }
                // If event changes its type, use the special event handlers for the changed type
         //兼容性处理,后面再说
                special = jQuery.event.special[type] || {};
                // If selector defined, determine special event api type, otherwise given type
                type = (selector ? special.delegateType : special.bindType) || type;
                // Update special based on newly reset type
                special = jQuery.event.special[type] || {};
                // handleObj is passed to all event handlers
         //创建事件处理对象,这里保存了事件的各种属性,包括事件名称,数据,guid,委托标签(selector),处理函数等等
                handleObj = jQuery.extend({
                    type: type,
                    origType: origType,
                    data: data,
                    handler: handler,
                    guid: handler.guid,
                    selector: selector,
                    needsContext: selector && jQuery.expr.match.needsContext.test(selector),
                    namespace: namespaces.join(".")
                }, handleObjIn);
                // Init the event handler queue if we're the first
         // 针对当前事件类型,初始化事件处理列队,如果是第一次使用,则执行初始化
                if (!(handlers = events[type])) {
handlers = events[type] = [];
handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false
//真正的逻辑在这里,先不考虑兼容性处理,把上面的事件处理器绑定到DOM对象中去,jQuery2.0.3不兼容低版本浏览器,所以addEventListener是通用的
if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
if (elem.addEventListener) {
elem.addEventListener(type, eventHandle, false);
}
}
}
//兼容性处理,先不看
if (special.add) {
special.add.call(elem, handleObj);
if (!handleObj.handler.guid) {
handleObj.handler.guid = handler.guid;
}
} // Add to the element's handler list, delegates in front
//如果有委托参数,将委托函数插入队列的前排头等舱位置
if (selector) {
handlers.splice(handlers.delegateCount++, 0, handleObj);
} else {
//否则追加到队列后面
handlers.push(handleObj);
} // Keep track of which events have ever been used, for event optimization
//标示当前事件曾经处理过,用于事件优化
jQuery.event.global[type] = true;
} // Nullify elem to prevent memory leaks in IE
// 设置为null避免IE中循环引用导致的内存泄露
elem = null;
},
整体思路是为当前的dom对象添加两个属性,一个是事件处理队列event_list,一个是事件处理器eventHandler,对用户输入的所有事件进行循环处理,创建事件对象保存到event_list中去,如果事件带有委托对象,则放到前排,否则追加到event_list,再把eventHandler绑定到DOM节点中这个事件中去,最终类似下面的结构:
<div id="#main_div"><span></span></div>
<script>
function handler1(){};
function handler2(){} $("#main_div").on("click",'span',handler1)
.on("click",handler2);
</script>
创建为: elemData = jQuery._data( elem );
elemData = {
events: {
click: {//Array[2]
0: {
data: undefined/{...},
guid: 2, //处理函数的id
handler: function handler1(){…},
namespace: "",
needsContext: false,
origType: "click",
selector: "span",
type: "click"
}
1: {
data: undefined,
guid: 3,
handler: function handler2(){…},
namespace: "",
needsContext: false,
origType: "click",
selector: undefined,
type: "click"
}
delegateCount: 1,//委托事件数量,有selector的才是委托事件
length: 2
}
}
handle: function ( e ) {…}{//click事件会触发这个处理函数
elem: document//属于handle对象的特征
}
}
回到上面的eventHandle = elemData.handle,当事件在浏览器触发时,会触发jQuery.event.dispatch.apply(eventHandle.elem, arguments) ,也就是执行dispatch方法:
2. dispatch方法:
      dispatch: function(event) {
            // Make a writable jQuery.Event from the native event object
            // 通过原生的事件对象创建一个可写的jQuery.Event对象
            event = jQuery.event.fix(event);
            var i, j, ret, matched, handleObj,
                handlerQueue = [],
                args = core_slice.call(arguments),
                handlers = (data_priv.get(this, "events") || {})[event.type] || [],
                special = jQuery.event.special[event.type] || {};
            // Use the fix-ed jQuery.Event rather than the (read-only) native event
            args[0] = event;
            // 事件的触发元素
            event.delegateTarget = this;
            // Call the preDispatch hook for the mapped type, and let it bail if desired
            if (special.preDispatch && special.preDispatch.call(this, event) === false) {
                return;
            }
            // Determine handlers
        //handlers就是前面的elementData.event,事件列表
       //调用event.handlers方法过滤当前
            handlerQueue = jQuery.event.handlers.call(this, event, handlers);
            // Run delegates first; they may want to stop propagation beneath us
            i = 0;
            // 遍历事件处理器队列{elem, handlerObjs}(取出来则对应一个包了),且事件没有阻止冒泡
            while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) {
                event.currentTarget = matched.elem;
                j = 0;
                // 如果事件处理对象{handleObjs}存在(一个元素可能有很多handleObjs),且事件不需要立刻阻止冒泡
                while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) {
                    // Triggered event must either 1) have no namespace, or
                    // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
                    // Triggered event must either 1) have no namespace, or
                    // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
                    // 触发的事件必须满足其一:
                    // 1) 没有命名空间
                    // 2) 有命名空间,且被绑定的事件是命名空间的一个子集
                    if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) {
                        event.handleObj = handleObj;
                        event.data = handleObj.data;
                        // 尝试通过特殊事件获取处理函数,否则使用handleObj中保存的handler(所以handleObj中还保存有handler(事件处理函数))
                        // handleObj.origType 定义的事件类型
                        // handleObj.handler 事件处理函数
                        // 终于到这里了,开始执行事件处理函数
                        ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler)
                            .apply(matched.elem, args);
                        // 检测是否有返回值存在
                        if (ret !== undefined) {
                            // 如果处理函数返回值是false,则阻止冒泡,阻止默认动作
                            if ((event.result = ret) === false) {
                                event.preventDefault();
                                event.stopPropagation();
                            }
                        }
                    }
                }
            }
            // Call the postDispatch hook for the mapped type
            if (special.postDispatch) {
                special.postDispatch.call(this, event);
            }
            return event.result;
        },
整个过程就是:
1. 当浏览器事件触发时,会触发dispatch方法并传入浏览器event对象。
2. jQuery显然不满意原生event对象,通过fix方法重写了event对象,除了继承了event对象,还添加了一些自己的属性,变成了event对象增强版。
3. 取出当前dom对象数据缓存中的Event列表,也就是事件列表。
4. 调用event.handler方法,把事件列表(handlers,也就是elemData.event)传递进去,对事件列表进行遍历查询,把需要执行的事件对象提取出来放到handlerQueue中去。
5. 对handlerQueue进行遍历,当存在handlerQueue对象并且事件没有阻止冒泡,则最终会调用事件对象函数,也就是调用之前绑定的事件对象函数。
上面是主要的4个步骤,实际上每个步骤都有很多细节要说:
1. 这个很简单,原生事件到jQuery事件的入口。
2. fix方法,返回了加强版的event对象,主要作用是浏览器兼容性处理,加入了jQuery的自己方法,以及可以创建自定义对象等等。
(1)首先对象的类型适配,keyEvent和mouseEvent的事件对象属性是不同的,这里通过fixHooks来进行适配,jQuery.event.props包含了事件对象的公用属性

然后mouseHooks和keyHooks分别包含了鼠标和键盘事件的不同对象,通过下面的逻辑进行合并,将公有属性和私有属性concat到一起,实现了动态适配。
if (!fixHook) {
        this.fixHooks[type] = fixHook =
        rmouseEvent.test(type) ? this.mouseHooks :
        rkeyEvent.test(type) ? this.keyHooks : {};
}
copy = fixHook.props ? this.props.concat(fixHook.props) : this.props;
然后参加event = new jQuery.Event(originalEvent);创建加强版event,Event对象下面会说,先执行下面逻辑
i = copy.length;
while (i--) {//将原生对象的属性值拷贝过来
prop = copy[i];
event[prop] = originalEvent[prop];
}
//兼容性处理,Cordova不支持target属性,这里添加上
if (!event.target) {
event.target = document;
}
// text节点不能是target,查找其父节点
if (event.target.nodeType === 3) {
event.target = event.target.parentNode;
}
//钩子方法,将keyEvent和mouseEvent的兼容性处理,见相应的filter方法,比如添加which属性,pageX和pageY属性等等。
return fixHook.filter ? fixHook.filter(event, originalEvent) : event;
最后返回一个全新的Event对象,该对象包含了旧对象的所有方法,还新增了兼容性处理的属性(pageX等),兼容了target属性等等。
(2)创建Event对象:Event构造函数的主要作用就是重写事件默认对象方法,兼容时间戳属性this.timeStamp = src && src.timeStamp || jQuery.now();因为部分浏览器这块有差异,这里统一处理。下面是对象方法:

重写了preventDefault,stopPropagation,stopImmediatePropagation方法,只是在原有的基础上新增了是否调用过的判断,是否调用过就是增加的isDefaultPrevented,isPropagationStopped和isImmediatePropagationStopped几个方法。、
3. handlers = (data_priv.get(this, "events") || {})[event.type] || [];取出事件列表
4. handlerQueue = jQuery.event.handlers.call(this, event, handlers);调用jQuery.event.handlers方法,这个方法主要做两个逻辑处理
(1)从事件源对象event.target开始,一层一层往父节点遍历,每遍历到一个父节点,再遍历event事件对象列表的前排带有委托标识(selector)的元素,看看当前节点是否能查询到委托元素(selector),如果能查到,说明当前节点是被委托处理事件的节点,也就是说源事件发生以后,当前节点是被委托需要响应事件的,就将该事件函数push到handlerQueue数组中,直到所有遍历完成。
此时handlerQueue数组保存的是当前节点需要处理的需要委托处理的方法。
(2)再把当前节点绑定的事件函数(也就是非委托直接绑定的事件函数) 添加到handlerQueue中去,此时handlerQueue包含了两种事件函数:
1) 其他节点委托到本节点的所有函数列表
2) 直接绑定到本节点的函数列表
到此就返回了当前事件需要响应的所有函数列表。
5. 事件函数调用:对于所有的事件函数列表,如果还有存在元素并且没有被阻止冒泡,遍历处理:
while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) {
}
最终的调用方法是:ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);
然后做判断:
// 检测是否有返回值存在
if (ret !== undefined) {
// 如果处理函数返回值是false,则阻止冒泡,阻止默认动作
if ((event.result = ret) === false) {
event.preventDefault();
event.stopPropagation();
}
}
if (special.postDispatch) {
                    special.postDispatch.call(this, event);
  }
return event.result;
到此,从事件的添加,到触发,到兼容处理,再到实际响应就完成了。
回顾一下jQuery的事件流程:
<div id='div_main'>
<div id="div_sub"></div>
</div> <script>
$("#div_main").on("click",function(){
console.log("self click");
});
$("#div_main").on("click","div",function(){
console.log("child click");
});
</script>
1. on方法将参数重新捋一遍然后调用add将两个方法添加到div_main的缓存数据中,形成以下数据结构:
elemData = jQuery._data( "div_main" );
elemData = {
events: {
click: {
0: {
data: undefined/{...},
guid: 1,
handler: function(){console.log("child cilck")},
namespace: "",
needsContext: false,
origType: "click",
selector: "div",
type: "click"
}
1: {
data: undefined,
guid: 2,
handler: function(){console.log("self click")},
namespace: "",
needsContext: false,
origType: "click",
selector: undefined,
type: "click"
}
delegateCount: 1,
length: 2
}
}
handle: function ( e ) {…}{//click事件会触发这个处理函数
elem: document//属于handle对象的特征
}
}
注意下有什么特点:
(1)委托事件放在前排头等舱
(2)delegateCount标识了有几个委托事件,如果为0则没有委托事件
(3)selector确定委托对象
然后添加一个handle回调函数
3. click事件触发,浏览器触发事件,调用handle方法,启动dispatch方法
4. 增强event对象为jQuery.Event对象,适配当前事件对象的属性,添加公共属性和私有属性,可以添加自定义属性,重新默认函数,触发jQuery.event.handlers方法。
5. jQuery.event.handlers:从事件源开始往上遍历,每一层遍历再遍历event事件对象数组,也就是上面的elemData.event,做一下判断
(1) 如果有委托事件(delegateCount>0),判断当前节点下是否能找到selector指定的元素,如果有说明需要处理委托事件,将事件触发对象和事件函数push到handlerQueue
(2) 将绑定到本节点的事件函数直接push到handlerQueue
6. 回到dispatch,对handlerQueue筛选出来的事件函数一一处理。
参考最上面的流程图
到此,不管是直接绑定的事件函数还是委托事件函数都已完成。
还有什么没说?对,还有兼容性处理special,现在我们把主线走通了,下一篇说一说特殊情况的兼容性处理。
彻底弄懂jQuery事件原理二的更多相关文章
- 彻底弄懂jQuery事件原理一
		
jQuery为我们提供了一个非常丰富好用的事件API,相对于浏览器自身的事件接口,jQuery有以下特点: 1. 对浏览器进行了兼容性处理,用户使用不需要考虑浏览器兼容性问题 2. 事件数据是保持在内 ...
 - 彻底弄懂JS事件委托的概念和作用
		
一.写在前头 接到某厂电话问什么是事件代理的时候,一开始说addEventListener,然后他说直接绑定新的元素不会报dom不存在的错误吗?然后我就混乱了,我印象中这个方法是可以绑定新节点的 ...
 - 知识扩展——(转)一篇文章彻底弄懂Base64编码原理
		
在互联网中的每一刻,你可能都在享受着Base64带来的便捷,但对于Base64的基础原理又了解多少?今天这篇博文带领大家了解一下Base64的底层实现. 一.Base64的由来 目前Base64已经成 ...
 - 一篇文章彻底弄懂Base64编码原理
		
在互联网中的每一刻,你可能都在享受着Base64带来的便捷,但对于Base64的基础原理又了解多少?今天这篇博文带领大家了解一下Base64的底层实现. Base64的由来 目前Base64已经成为网 ...
 - 一篇文章彻底弄懂Base64编码原理(转载)
		
在互联网中的每一刻,你可能都在享受着Base64带来的便捷,但对于Base64的基础原理又了解多少?今天这篇博文带领大家了解一下Base64的底层实现. Base64的由来 目前Base64已经成为网 ...
 - 弄懂goroutine调度原理
		
goroutine简介 golang语言作者Rob Pike说,"Goroutine是一个与其他goroutines 并发运行在同一地址空间的Go函数或方法.一个运行的程序由一个或更多个go ...
 - js进阶 12-2 彻底弄懂JS的事件冒泡和事件捕获
		
js进阶 12-2 彻底弄懂JS的事件冒泡和事件捕获 一.总结 一句话总结:他们是描述事件触发时序问题的术语.事件捕获指的是从document到触发事件的那个节点,即自上而下的去触发事件.相反的,事件 ...
 - 解密jQuery事件核心 - 委托设计(二)
		
第一篇 http://www.cnblogs.com/aaronjs/p/3444874.html 从上章就能得出几个信息: 事件信息都存储在数据缓存中 对于没有特殊事件特有监听方法和普通事件都用ad ...
 - 彻底弄懂JS的事件冒泡和事件捕获(不推荐阅读)
		
由于搬去敌台了,好久没来博客园,今天无意中翻到有“误认子弟”的评论,这里特意做个说明. 本文中关于事件冒泡和事件捕获的描述和例子都是OK的,错就错在后面用jquery去展示了利用事件冒泡的例子有误,其 ...
 
随机推荐
- go-005-变量、常量
			
概述 变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念.变量可以通过变量名访问. Go 语言变量名由字母.数字.下划线组成,其中首个字母不能为数字. 声明变量的一般形式是使用 var 关 ...
 - Debian更新软件源提示There is no public key available for the following key IDs的解决方法
			
今天装了的debian7.0 但是更新软件源的时候出错 提示 W: There is no public key available for the following key IDs: 9D6D8F ...
 - 禁止复制操作 --《C++必知必会》条款32
			
class NoCopy{ private: //声明为私有的,则外部不可访问,即:不可复制 NoCopy(const NoCopy & );//复制构造函数 NoCopy & ope ...
 - 梅尔频率倒谱系数(MFCC) 学习笔记
			
最近学习音乐自动标注的过程中,看到了有关使用MFCC提取音频特征的内容,特地在网上找到资料,学习了一下相关内容.此笔记大部分内容摘自博文 http://blog.csdn.net/zouxy09/ar ...
 - Linux 笔记 #01# 搭建 Python 环境 & vim 代码高亮
			
日常收集 vim editor: How do I enable and disable vim syntax highlighting? 搭建 Python 环境 vim editor: How d ...
 - Python笔记 #18# Pandas: Grouping
			
10 Minutes to pandas 引 By “group by” we are referring to a process involving one or more of the foll ...
 - bzoj 4443: [Scoi2015]小凸玩矩阵
			
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 149 Solved: 81[Submit][Status][Discuss] Description ...
 - 【前端】特效-Javascript实现购物页面图片放大效果
			
实现效果 实现代码: <!DOCTYPE html> <html> <head> <title>购物图片放大</title> <met ...
 - ubuntu18.04 install pip
			
1. environment release version: bionic kernel version:4.15.0-29-generic 2.install pip 2.1 sudo apt-g ...
 - Mac下安装hexo Error: Cannot find module './build/Release/DTraceProviderBindings 解决
			
参考: Github:Mac 下已经装了hexo,仍旧报错 官方文档 $ npm install hexo --no-optional if it doesn't work try $ npm uni ...