Fork me on GitHub

解密jQuery事件核心 - 模拟事件(四)

前几章已经把最核心的实现都分解过了,这一章我们看看jQuery是如何实现事件模拟的

在Internet Explorer 8和更低,一些事件changesubmit本身不冒泡,但jQuery修改这些冒泡,创建一致的跨浏览器的行为。

焦点事件

blur :

在这个事件触发前,元素已经失去焦点,不冒泡,同步触发。target 指向当前失去焦点的元素。

focus:

在这个事件触发前,元素已经得到焦点,不冒泡,同步触发。target 指向当前得到焦点的元素。

 


与此同时DOM Level 3 事件模块 还定义了 focusin ,focusout 以及 DOMFocusIn ,DOMFocusOut 四个事件。

focusin :

在当前元素获得焦点前以及相关元素失去焦点前触发,可冒泡,同步触发。target 指向当前将要获得焦点的元素,relatedTarget 指向失去焦点的元素

focusout :

在当前失去焦点前触发,可冒泡,同步触发。target 指向当前将要失去焦点的元素,relatedTarget 指向将要失去焦点的元素。

DOMFocusIn :

在这个事件触发前,元素已经得到焦点,可冒泡,同步触发。target 指向当前得到焦点的元素。

DOMFocusOut :

在这个事件触发前,元素已经没有焦点,可冒泡,同步触发。target 指向当前失去焦点的元素

 


事件的兼容性支持

image


1, 所有 IE 版本均支持focusin/focusout事件(注意:IE6/7/8中不支持el.addEventListener方法)。
2, Opera 最强悍即支持attachEvent,又支持addEventListener。且这两种方式添加事件均支持focusin/focusout事件。
3, Safari/Chrome  给人一个惊喜,虽然el.onfocusin方式不支持,但 addEventListener方式却支持。因此想让Safari/Chrome中支持focusin事件,只能使用addEventListener方式添加事件。
4, Firefox 任何一种添加事件方式都不支持 focusout/focuso

那么如何在所有的平台上都兼容focusin/focusout?

 


jQuery.event.special方法

这个方法在event.add,event.dispatch等几个事件的处理地方都会被调用到,jQuert.event.special 对象用于某些事件类型的特殊行为和属性

换句话说就是某些事件不是大众化的的事件,不能一概处理,比如 load 事件拥有特殊的 noBubble 属性,可以防止该事件的冒泡而引发一些错误

所以需要单独针对处理,但是如果都写成判断的形式,显然代码结构就不合理了,而且不方便提供给用户自定义扩展

在webkit下的截图,特殊事件类型

image

大体上针对9种事件,不同情况下处理hack,我们具体分析下焦点事件兼容冒泡处理,处理大同小异

 


jQuery.event 事件机制 focusin/ focusout 事件

image

针对focusin/ focusout 事件jQuery.event.special扩充2组处理机制,

special.setup方法主要是来在Firefox中模拟focusin和focusout事件的,因为各大主流浏览器只有他不支持这两个事件。

由于这两个方法支持事件冒泡,所以可以用来进行事件代理

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 );
        }
    },
    teardown: function() {
        if ( --attaches === 0 ) {
            document.removeEventListener( orig, handler, true );
        }
    }
};

前面的分析我们就知道通过事件最终都是通过add方法绑定的,也就是addEventListener方法绑定的,但是在add方法之前会有一个过滤分支

image

以前看不懂代码,现在回过来恍然大悟了,原来这个方法是这样用的

所以最终代码会跑到各种的Hack中了,

可见对focusin/ focusout 的处理,没有用通用的方法,而且是直接用的special.setup中的绑定

几个重点

1 绑定的是focusin/ focusout 事件,内部确换成了focus/blur事件

2 document.addEventListener( orig, handler, true );事件绑在document上,最后是true,用的捕获绑定

3 jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );方法

 


为什么用捕获?

因为火狐不支持focusin/ focusout事件,所以要找个所有浏览器都兼容类似事件,对了那就是focus/blur,

但是focus/blur不能冒泡丫,怎么办?

咱不是还有捕获吗?

那么利用捕获怎么模拟出冒泡呢?

 


jQuery.event.simulate方法

jQuery.event.simulate = function( type, elem, event, bubble ) {
    // 重写事件
    var e = jQuery.extend(
        new jQuery.Event(),
        event,
        { type: type,
            isSimulated: true,
            originalEvent: {}
        }
    );
    // 如果要冒泡
    if ( bubble ) {
        // 利用jQuery.event.trigger模拟触发事件
        jQuery.event.trigger( e, null, elem );
    } else {
        // 否则利用jQuery.event.dispatch来执行处理
        jQuery.event.dispatch.call( elem, e );
    }
    // 如果需要阻止默认操作,则阻止
    if ( e.isDefaultPrevented() ) {
        event.preventDefault();
    }
}

可以看到focusin/ focusout 可冒泡事件实现原理是

1 focusin 事件添加事件处理程序时,jQuery 会在 document 上会添加 handler 函数

2 在事件捕获阶段监视特定元素的 focus/ blur 动作,捕获行为发生在 document 对象上,这样才能有效地实现所有元素都能可以冒泡的事件。

3 程序监视到存在 focus/ blur 行为,就会触发绑定在 document 元素上的事件处理程序,该事件处理程序在内部调用 simulate 逻辑触发事件冒泡,以实现我们希望的可以冒泡事件。

4 之后利用jQuery.event.trigger模拟触发事件,把从target-document的元素都过滤出来,分析每个节点上是否绑定了事件句柄,依次处理,按照一定的规范,比如是否有事件阻止之类的,这里就不再重复分析了jQuery.event.trigger  http://www.cnblogs.com/aaronjs/p/3452279.html

 


总结

咋一看其实原理都挺简单的, 但是jQuery为了实现兼容统一,可谓煞费苦心了,把事件冒泡与捕获都统一模拟了一遍

  1. jQuery为统一原生Event对象而封装的jQuery.Event类,封装了preventDefault,stopPropagation,stopImmediatePropagation原生接口,可以直接捕获到用户的行为
  2. 由核心组件 jQuery.cache 实现注册事件处理程序的存储,实际上绑定在 DOM元素上的事件处理程序只有一个,即 jQuery.cache[elem[expando]].handle 中存储的函数,该函数在内部调用 jQuery.event.dispatch(event) 实现对该DOM元素特定事件的缓存的访问,并依次执行这些事件处理程序。
  3. jQuery.event.add(elem, types, handler, data, selector) 方法用于给特定elem元素添加特定的事件 types([type.namespace, type.namespace, ...])的事件处理程序 handler, 通过第四个参数 data 增强执行当前 handler 事件处理程序时的 $event.data 属性,以提供更灵活的数据通讯,而第五个元素用于指定基于选择器的委托事件
  4. namespace 命名空间机制,namespace 机制可以对事件进行更为精细的控制,开发人员可以指定特定空间的事件,删除特定命名空间的事件,以及触发特定命名空间的事件。这使得对事件处理机制的功能更加健
  5. jQuert.event.special 对象用于某些事件类型的特殊行为和属性。比如 load 事件拥有特殊的 noBubble 属性,可以防止该事件的冒泡而引发一些错误。总的来说,有这样一些方法和属性:
  6. jQuery.event.simulate(type, elem, event, bubble)模拟事件并立刻触发方法,可用于在DOM元素 elem 上模拟自定义事件类型 type,参数 bubble用于指定该事件是否可冒泡,event 参数表示 jQuery 事件对象 $event。 模拟事件通过事件对象的isSimulated属性为 true 表示这是模拟事件。该方法内部调用 trigger() 逻辑 或 dispatch() 逻辑立刻触发该模拟事件。该方法主要用于修正浏览器事件的兼容性问题,比如模拟出可冒泡的 focusin/ focusout 事件,修正IE中 change 事件的不可冒泡问题,修正IE中 submit事件不可冒泡问题
  7. jQuery.event.dispatch(event) 方法在处理事件委托机制时,依赖委托节点在DOM树的深度安排优先级,委托的DOM节点层次越深,其执行优先级越高。而其对于stopPropagation的处理有些特殊,在事件委托情况下并不一定会调用绑定在该DOM元素上的该类型的所有事件处理程序,而依赖于委托的事件处理程序的执行结果,如果低层委托的事件处理程序声明了停止冒泡,那么高层委托的事件以及自身绑定事件就不会被执行,这拓展了 DOM 委托机制的功能。
  8. jQuery.event.trigger(event | type, data, elem, onlyHandlers) 方法提供开发人员以程序方式触发特定事件的接口,该方法的第一个参数可以是 $event/ event 对象 ,也可以是某个事件类型的字符串 type; 第二个参数 data 用于扩展该事件触发时事件处理程序的参数规模,用于传递一些必要的信息。 elem参数表示触发该事件的DOM元素;最后该方法在默认情况下,其事件会冒泡,并且在有默认动作的情况下执行默认行为,但是如果指定了 onlyHandlers 参数,该方法只会触发绑定在该DOM元素上的事件处理程序,而不会引发冒泡和默认动作,也不会触发特殊的 trigger 行为。
  9. …………………
posted on 2013-12-19 09:20  【艾伦】  阅读(12136)  评论(6编辑  收藏  举报