jQuery源码分析--event事件绑定(下)

来源:互联网 发布:在线抽奖软件 编辑:程序博客网 时间:2024/06/03 21:45

上文提到,

  • 事件的绑定函数one(),bind(),delegate()等方法内部都是统一调用jQuery.fn.on()方法
  • jquery.fn,on方法重载所有的输入,并调用jQuery.event.add方法,添加elemData属性(即把每个元素对应的事件缓存到一个缓冲区),包装事件,普通事件调用addeventlistener事件,回调depatch函数。特殊事件转交special.setup来处理。

具体代码如下:

   // Only use addEventListener if the special events handler returns false                //如果特殊事件returnfalse,获取失败,就用addEventListener方法啦                if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {                    if ( elem.addEventListener ) {                        elem.addEventListener( type, eventHandle, false );                    }                }

jQuery.event.special

//4582行special: {        load: {            // Prevent triggered image.load events from bubbling to window.load            //防止window.load事件的继续冒泡,load事件不允许冒泡            noBubble: true        },        focus: {            // Fire native event if possible so blur/focus sequence is correct            //如果可能的话立即执行原生事件,这样说明blur/focus事件是正确的            trigger: function() {                if ( this !== safeActiveElement() && this.focus ) {                    this.focus();                    //这里也取消冒泡和默认事件                    return false;                }            },            delegateType: "focusin"        },        blur: {            trigger: function() {                if ( this === safeActiveElement() && this.blur ) {                    this.blur();                    return false;                }            },            delegateType: "focusout"        },        click: {            // For checkbox, fire native event so checked state will be right            //对于checkbox标签,直接执行原生事件,这样选择状态就是对的            trigger: function() {                if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {                    this.click();                    return false;                }            },            // For cross-browser consistency, don't fire native .click() on links            //为了跨浏览器的兼容性,不直接执行带link的click原生事件            _default: function( event ) {                return jQuery.nodeName( event.target, "a" );            }        },        beforeunload: {            postDispatch: function( event ) {                // Support: Firefox 20+                // Firefox doesn't alert if the returnValue field is not set.                //兼容firefox 20+                //如果没有设置指定的字段是Firefox不告警。                if ( event.result !== undefined && event.originalEvent ) {                    event.originalEvent.returnValue = event.result;                }            }        }    },

special列举了一些特殊事件,处理兼容性的问题或特殊的实现。

special.setup

//4768行// Support: Firefox, Chrome, Safari// Create "bubbling" focus and blur events// 创建冒泡的focus和blur事件,即focusin和focusoutif ( !support.focusinBubbles ) {    jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {        // Attach a single capturing handler on the document while someone wants focusin/focusout        // 当需要focusin/focusout时,在文档上附上一个简单的捕获处理函数        var handler = function( event ) {                jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );            };       // 对需要修复的特殊事件添加方法        jQuery.event.special[ fix ] = {            setup: function() {                var doc = this.ownerDocument || this,                    attaches = data_priv.access( doc, fix );                if ( !attaches ) {                    doc.addEventListener( orig, handler, true );                }                data_priv.access( doc, fix, ( attaches || 0 ) + 1 );            },            teardown: function() {                var doc = this.ownerDocument || this,                    attaches = data_priv.access( doc, fix ) - 1;                if ( !attaches ) {                    doc.removeEventListener( orig, handler, true );                    data_priv.remove( doc, fix );                } else {                    data_priv.access( doc, fix, attaches );                }            }        };    });}

special.setup解决的问题

  • 在Firefox中模拟focusin和focusout事件的,因为各大主流浏览器只有他不支持这两个事件。然后调用jQuery.event.simulate()方法来模拟事件触发。
  • 对需要修复的特殊事件添加方法,即上文提到的special数组里的几种情况

jQuery.event.simulate

simulate: function( type, elem, event, bubble ) {        // Piggyback on a donor event to simulate a different one.        //搭载在一个新的捐赠事件上去触发另一个不同的事件        // Fake originalEvent to avoid donor's stopPropagation, but if the        // simulated event prevents default then we do the same on the donor.        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();        }    }

这里我们发现,这里针对冒泡事件是不冒泡事件分别用trigger事件来触发和depatch来触发。

jQuery.event.trigger

//4259行trigger: function( event, data, elem, onlyHandlers ) {        var i, cur, tmp, bubbleType, ontype, handle, special,            eventPath = [ elem || document ],            type = hasOwn.call( event, "type" ) ? event.type : event,            //命名空间的过滤            namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];        cur = tmp = elem = elem || document;        // Don't do events on text and comment nodes        //在text或comment节点上阻止事件        if ( elem.nodeType === 3 || elem.nodeType === 8 ) {            return;        }        // focus/blur morphs to focusin/out; ensure we're not firing them right now        //focus/blur 变成 focusin/out,保证我们现在不会触发它们        if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {            return;        }        if ( type.indexOf(".") >= 0 ) {            // Namespaced trigger; create a regexp to match event type in handle()            //命名空间的触发,创建一个正则表达式去匹配事件类型            namespaces = type.split(".");            type = namespaces.shift();            namespaces.sort();        }        ontype = type.indexOf(":") < 0 && "on" + type;        // Caller can pass in a jQuery.Event object, Object, or just an event type string        //包装event事件        event = event[ jQuery.expando ] ?            event :            new jQuery.Event( type, typeof event === "object" && event );        // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)        event.isTrigger = onlyHandlers ? 2 : 3;        event.namespace = namespaces.join(".");        event.namespace_re = event.namespace ?            new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :            null;        // Clean up the event in case it is being reused        //清理事件防止重复使用        event.result = undefined;        if ( !event.target ) {            event.target = elem;        }        // Clone any incoming data and prepend the event, creating the handler arg list        //克隆任何传入的数据和预先设置的事件,创建处理程序列表        data = data == null ?            [ event ] :            jQuery.makeArray( data, [ event ] );        // Allow special events to draw outside the lines        //允许特殊事件        special = jQuery.event.special[ type ] || {};        if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {            return;        }        // Determine event propagation path in advance, per W3C events spec (#9951)        //对每一个W3C事件,提前确定事件的传播路径        // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)        //冒泡到document再到文档,看全局var变量??        if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {            bubbleType = special.delegateType || type;            if ( !rfocusMorph.test( bubbleType + type ) ) {                cur = cur.parentNode;            }            for ( ; cur; cur = cur.parentNode ) {                eventPath.push( cur );                tmp = cur;            }            // Only add window if we got to document (e.g., not plain obj or detached DOM)            if ( tmp === (elem.ownerDocument || document) ) {                eventPath.push( tmp.defaultView || tmp.parentWindow || window );            }        }        //对 eventPath 进行模拟冒泡的触发        // Fire handlers on the event path        i = 0;        while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {            event.type = i > 1 ?                bubbleType :                special.bindType || type;            // jQuery handler            handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );            if ( handle ) {                handle.apply( cur, data );            }            // Native handler            handle = ontype && cur[ ontype ];            if ( handle && handle.apply && jQuery.acceptData( cur ) ) {                event.result = handle.apply( cur, data );                if ( event.result === false ) {                    event.preventDefault();                }            }        }        event.type = type;        // If nobody prevented the default action, do it now        //如果没有人阻止错误行为,就执行        if ( !onlyHandlers && !event.isDefaultPrevented() ) {            if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&                jQuery.acceptData( elem ) ) {                // Call a native DOM method on the target with the same name name as the event.                //如果事件名字一样,对target调用原生DOM方法                // Don't do default actions on window, that's where global variables be (#6170)                if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {                    // Don't re-trigger an onFOO event when we call its FOO() method   不要重复触发                    tmp = elem[ ontype ];                    if ( tmp ) {                        elem[ ontype ] = null;                    }                    // Prevent re-triggering of the same event, since we already bubbled it above                    jQuery.event.triggered = type;                    elem[ type ]();                    jQuery.event.triggered = undefined;                    if ( tmp ) {                        elem[ ontype ] = tmp;                    }                }            }        }        return event.result;    },

其实还是没太看懂。但是trigger 的处理就是模拟冒泡的一个调度,具体的触发还是交给 jQuery.event.dispatch 方法了。
trigger函数具体工作如下:
- 命名空间的过滤
- 模拟事件对象
- 制作一个触发的路径队列eventPath
- 对 eventPath 进行模拟冒泡的触发
- 在一个层级调用 dispatch 处理各自的内部事件关系(委托)

从这一个功能点上我们就不难发现为什么 jQuery 要设计元素与数据分离了。

  • 如果是直接绑定的话就完全无法通过 trigger 的机制调用。trigger
    的实现首先得益于事件的分离机制,因为没有直接把事件相关的与元素直接绑定采用了分离处理,所以我们通过 trigger 触发addEventListener 触发的处理流程都是一致的,不同的只是触发的方式而已。
  • 通过 trigger触发的事件是没有事件对象、冒泡这些特性的,所以我们需要有一个方法能模拟出事件对象,然后生成一个遍历树模拟出冒泡行为,那么这个任务就交给了trigger 方法了。
0 0
原创粉丝点击