jQuery之.on()方法

還不是徹底清楚如何使用.on()進行jQuery事件綁定的同窗先看這裏http://api.jquery.com/on/html

jQuery綁定事件的方法有幾種,推薦使用.on()方法綁定,緣由有兩點:node

1.on()方法能夠綁定動態添加到頁面元素的事件

好比動態添加到頁面的DOM元素,用.on()方法綁定的事件不須要關心註冊該事件的元素什麼時候被添加進來,也不須要重複綁定。有的同窗可能習慣於用.bind()、.live()或.delegate(),查看源碼就會發現,它們實際上調用的都是.on()方法,而且.live()方法在jQuery1.9版本已經被移除。jquery

bind: function( types, data, fn ) {
    return this.on( types, null, data, fn );
},

live: function( types, data, fn ) {
    jQuery( this.context ).on( types, this.selector, data, fn );
    return this;
},

delegate: function( selector, types, data, fn ) {
    return this.on( types, selector, data, fn );
}

移除.on()綁定的事件用.off()方法。chrome

 

2.on()方法綁定事件能夠提高效率

不少文章都提到了利用事件冒泡和代理來提高事件綁定的效率,大多都沒列出具體的差異,因此爲了求證,我作一個小測試。api

假設頁面添加了5000個li,用chrome開發者工具Profiles測試頁面載入時間。瀏覽器

普通綁定(姑且這麼稱呼它)app

$('li').click(function(){
    console.log(this)
});

綁定過程的執行時間函數

2013-08-13_190358

普通綁定至關於在5000li上面分別註冊click事件,內存佔用約4.2M,綁定時間約爲72ms。工具

.on()綁定源碼分析

$(document).on('click', 'li', function(){
    console.log(this)
})

綁定過程的執行時間

2013-08-13_191010

.on()綁定利用事件代理,只在document上註冊了一個click事件,內存佔用約2.2M,綁定時間約爲1ms。

.on()源碼分析

.on()方法分析包含其調用的兩個主要方法:
.add()進行事件註冊

.dispatch()進行事件代理

/* jQuery 1.10.2 */
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
    var type, origFn;

    // Types can be a map of types/handlers
    if ( typeof types === "object" ) {
        // ( types-Object, selector, data )
        if ( typeof selector !== "string" ) {
            // ( types-Object, data )
            data = data || selector;
            selector = undefined;
        }
        // 遍歷types對象,針對每個屬性綁定on()方法
        // 將types[type]做爲fn傳入
        for ( type in types ) {
            this.on( type, selector, data, types[ type ], one );
        }
        return this;
    }

    // 參數修正
    // jQuery這種參數修正的方法很好
    // 能夠兼容多種參數形式
    // 可見在靈活調用的背後作了不少處理
    if ( data == null && fn == null ) {
        // ( types, fn )
        fn = selector;
        data = selector = undefined;
    } else if ( fn == null ) {
        if ( typeof selector === "string" ) {
            // ( types, selector, fn )
            fn = data;
            data = undefined;
        } else {
            // ( types, data, fn )
            fn = data;
            data = selector;
            selector = undefined;
        }
    }
    if ( fn === false ) {
        // fn傳入false時,阻止該事件的默認行爲
        // function returnFalse() {return false;}
        fn = returnFalse;
    } else if ( !fn ) {
        return this;
    }

    // one()調用on()
    if ( one === 1 ) {
        origFn = fn;
        fn = function( event ) {
            // Can use an empty set, since event contains the info
            // 用一個空jQuery對象,這樣可使用.off方法,
            // 而且event帶有remove事件須要的信息
            jQuery().off( event );
            return origFn.apply( this, arguments );
        };
        // Use same guid so caller can remove using origFn
        // 事件刪除依賴於guid
        fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
    }

    // 這裏調用jQuery的each方法遍歷調用on()方法的jQuery對象
    // 如$('li').on(...)則遍歷每個li傳入add()
    // 推薦使用$(document).on()或者集合元素的父元素
    return this.each( function() {
        jQuery.event.add( this, types, fn, data, selector );
    });
},

// 事件註冊
add: function( elem, types, handler, data, selector ) {
    var tmp, events, t, handleObjIn,
        special, eventHandle, handleObj,
        handlers, type, namespaces, origType,
        elemData = jQuery._data( elem );

    // Don't attach events to noData or
    // text/comment nodes (but allow plain objects)
    // 不符合綁定條件的節點
    if ( !elemData ) {
        return;
    }

    // Caller can pass in an object of custom data in lieu of the handler
    // 傳入的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
    // 爲handler分配一個ID,用於以後的查找或刪除
    if ( !handler.guid ) {
        handler.guid = jQuery.guid++;
    }

    // Init the element's event structure and main handler,
    // if this is the first
    // 初始化events結構
    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
        // 添加elem爲eventHandle的屬性,防止IE非本地事件的內存泄露?
        // 搜索整個源碼,只有110行用到了eventHandle.elem
        eventHandle.elem = elem;
    }

    // Handle multiple events separated by a space
    // 處理多個以空格分隔的事件類型
    types = ( types || "" ).match( core_rnotwhite ) || [""];
    t = types.length;
    while ( t-- ) {
        tmp = rtypenamespace.exec( types[t] ) || [];
        type = origType = tmp[1];
        // 存儲全部命名空間
        namespaces = ( tmp[2] || "" ).split( "." ).sort();

        // 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
        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
        // 初始化handler隊列,只初始化一次
        if ( !(handlers = events[ type ]) ) {
            handlers = events[ type ] = [];
            handlers.delegateCount = 0;

            // Only use addEventListener/attachEvent
            // if the special events handler returns false
            if ( !special.setup ||
            special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
                // Bind the global event handler to the element
                // 二級DOM事件/IE事件模型
                // eventHandle會調用jQuery.event.dispatch進行事件代理
                if ( elem.addEventListener ) {
                    elem.addEventListener( type, eventHandle, false );

                } else if ( elem.attachEvent ) {
                    elem.attachEvent( "on" + type, eventHandle );
                }
            }
        }

        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
    // 將變量置空,防止循環引用致使IE內存泄露
    elem = null;
},

// 事件代理
dispatch: function( event ) {

    // Make a writable jQuery.Event from the native event object
    // jQuery定義的event對象,兼容標準事件模型與IE事件模型
    event = jQuery.event.fix( event );

    var i, ret, handleObj, matched, j,
        handlerQueue = [],
        args = core_slice.call( arguments ),
        handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
        special = jQuery.event.special[ event.type ] || {};

    // Use the fix-ed jQuery.Event rather than the (read-only) native event
    // 使用jQuery.Event代替瀏覽器的event
    args[0] = event;
    // 事件的代理節點,好比document
    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
    // 遍歷事件發生節點至代理節點之間的全部節點
    // 匹配每個發生節點=?綁定節點
    handlerQueue = jQuery.event.handlers.call( this, event, handlers );

    // Run delegates first; they may want to stop propagation beneath us
    i = 0;
    // 遍歷匹配的節點,而且沒有被阻止冒泡
    while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
        event.currentTarget = matched.elem;

        j = 0;
        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).
            if ( !event.namespace_re ||
             event.namespace_re.test( handleObj.namespace ) ) {

                event.handleObj = handleObj;
                event.data = handleObj.data;

                // 傳入綁定事件的具體節點,調用事件發生函數
                ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle ||
                 handleObj.handler )
                        .apply( matched.elem, args );

                if ( ret !== undefined ) {
                    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;
}
相關文章
相關標籤/搜索