最终传入事件处理程序的 event 其实已经被 jQuery 做过标准化处理,
其原有的事件对象则被保存于 event 对象的 originalEvent 属性之中,
每个 event 都是 jQuery.Event 的实例,其原型链中保存有六个方法,
代码如下 | 复制代码 |
jQuery.Event.prototype = { |
对于取消事件默认行为与向上冒泡大家肯定不陌生,让我们把重点放在第三个方法上,
从源码上我们知道其调用了stopPropagation方法,所以他第一个作用就是取消冒泡。
代码如下 | 复制代码 |
for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { |
让我先来简单阐述下 jQuery 的事件绑定机制,这样能让大家更好理解他的第二个作用。
使用 jQuery 绑定的事件处理程序会按其类型与绑定顺序存于节点相应的events对象中,
当事件触发时则使用 $._data 取出events中对应事件的处理程序列队以便后续遍历执行。
内层循环的 matched.matches 中保存事件触发时当前节点需执行的事件处理程序列队,
所以当 isImmediatePropagationStopped 为 true 时则会阻止当前事件下该节点的后续事件处理程序执行。
最后来个简单的
代码如下 | 复制代码 |
<!DOCTYPE HTML> $("body").delegate("#J_btn", "click", function() { var $btn = $("#J_btn"); $btn.click(function(event) { $btn.click(function() { </script> |
希望能帮助大家更好的理解!
jQuery.event 事件机制 focusin/ focusout 事件
首先来看 jQuery.simulate() 方法,该方法基于原生事件实现可冒泡事件。源码如下:
代码如下 | 复制代码 |
simulate: function(type, elem, event, bubble) { var evt = jQuery.extend(new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); if (bubble) { jQuery.event.trigger(evt, null, elem); // 直接程序触发,这里可冒泡 } else { jQuery.event.dispatch.call(elem, evt); } if (evt.isDefaultPrevented()) { event.preventDefault(); //由于模拟的缘故,基础对象应该同时更改 } } |
focusin/ focusout 可冒泡事件实现原理是,在事件捕获阶段监视特定元素的 focus/ blur 动作,捕获行为发生在 document 对象上,这样才能有效地实现所有元素都能可以冒泡的事件。 一旦程序监视到存在 focus/ blur 行为,就会触发绑定在 document 元素上的事件处理程序,该事件处理程序在内部调用 simulate 逻辑触发事件冒泡,以实现我们希望的可冒泡事件。源码如下:
代码如下 | 复制代码 |
jQuery.each({focus: "focusin", blur: "focusout"}, function(orig, fix) { var attaches = 0, handler = function(event) { jQuery.event.simulate(fix, event.target, jQuery.event.fix(event), true); }; jQuery.event.special[fix] = { setup: function() { if (attaches++ === 0) { document.addEventListener(orig, handler, true); // 在 focus/blur 事件的捕获阶段添加事件监听,直接在 document 上监视,同时防止过多绑定 } }, teardown: function() { if (--attaches === 0) { document.removeEventListener(orig, handler, true); } } }; }); |
下面我们来分析我们应用 focusin 的过程,以便更清楚地理解其行为。
代码如下 | 复制代码 |
$("p").focusin(function() { alert('yes!'); }); |
当我们第一次给 focusin 事件添加事件处理程序时,jQuery 会在 document 上会添加 handler 函数。之后每次用户触发 focus 事件时,浏览器都将捕获该事件并第一时间触发 handler 函数, handler 函数在其内部模拟了事件冒泡过程(trigger),由此实现可冒泡事件。
关于 focusin/focus 事件对 在 trigger 方法中的相关逻辑。源码如下:
代码如下 | 复制代码 |
trigger: function(event, data, elem, onlyHandlers) { // .... // rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, // 这里用于避免: 触发 elem.focus 默认行为时,二次触发 focusin 行为,因为已执行过。 if (rfocusMorph.test(type + jQuery.event.triggered)) { return; } // .... // 特殊事件,主要是解决 focus 机制。如果 trigger 返回 false,则直接返回 special = jQuery.event.special[type] || {}; if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { return; } if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { var bubbleType = special.delegateType || type; if (!rfocusMorph.test(bubbleType + type)) { // type= focus/blur 这样可以在 elem 上同时触发 focus 和 focusin 事件 cur = cur.parentNode; } // ... } // .... }, |
一: 避免二次触发 focusin 事件,当我们执行 elem.focus() 时,document 会再次捕获该事件并尝试再触发 focusin 事件。
代码如下 | 复制代码 |
if (rfocusMorph.test(type + jQuery.event.triggered)) { return; } |
二: 当 trigger('focus') 时,尽可能地应用原生事件,但不会冒泡。
代码如下 | 复制代码 |
if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { |
三: 如果不满足应用原生事件的条件,则在当前元素上触发 focus 和 focusin 事件,并以 focusin 类型的事件向上冒泡。
代码如下 | 复制代码 |
var bubbleType = special.delegateType || type; // type= focus/blur, 这样可以在 elem 上同时触发 focus 和 focusin 事件 if (!rfocusMorph.test(bubbleType + type)) { cur = cur.parentNode; }event.type = i > 1 ? bubbleType : special.bindType || type; |
二、 mouseenter/ mouseleave 高阶事件
mouseenter/ leave 高阶事件依赖于 special['mouseenter'].handle 方法来实现。在该方法内部,会有程序的判断逻辑,如果程序判断当前的 mouseover 事件满足特定条件,该事件就成为 mouseenter 事件,才调用对应的事件处理程序。
mouseenter/ leave 高阶事件依赖于原生的 mouseover/mouseouter 事件,如图所示:
其 handle 方法的源码如下:
代码如下 | 复制代码 |
// if related is outside the target. return ret; |
观察源码我们可以发现,当通过程序触发高阶事件时,比如 trigger('mouseenter'),实际上需要触发的是对应的原生事件。因此,下面的代码的结果会令你耳目一新,因为这里绑定的 mouseenter 都会有所响应:
代码如下 | 复制代码 |
var $inner = $('#inner'); var $outer = $('#outer'); $inner.on('mouseover', function(){ $(this).css('backgroundColor', '#444'); }); $inner.on('mouseenter', function(event){ alert(event.relatedTarget); }); $outer.on('mouseenter', function(){ var $this = $(this); $this.css('backgroundColor', '#000'); }); $("p").click(function () { $inner.trigger('mouseenter');} ); |
三、浏览器兼容性修正
依赖于 postDispatch 逻辑,调用 simulate() 方法实现事件的冒泡。
本文完。