天天看點

第五節:JQuery架構源碼簡析(3)

(續2)

9、DOM

我們将jQuery有關DOM子產品的代碼放在檔案jquery.extend.dom.js中。

function jQuery_extend_dom(jQuery){

    jQuery.fn.extend({

        text: function( text ) {

            if ( jQuery.isFunction(text) ) {               

                return this.each(function(i) {

                    var self = jQuery( this );

                    self.text( text.call(this, i, self.text()) );

                });

            }

            if ( typeof text !== "object" && text !== undefined ) {

                return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

            return jQuery.text( this );

        },

        wrapAll: function( html ) {

            if ( jQuery.isFunction( html ) ) {

                    jQuery(this).wrapAll( html.call(this, i) );

            if ( this[0] ) {

                // The elements to wrap the target around

                var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);

                if ( this[0].parentNode ) {

                    wrap.insertBefore( this[0] );

                }

                wrap.map(function() {

                    var elem = this;

                    while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {

                        elem = elem.firstChild;

                    }

                    return elem;

                }).append( this );

            return this;

        wrapInner: function( html ) {

                    jQuery(this).wrapInner( html.call(this, i) );

            return this.each(function() {

                var self = jQuery( this ),

                    contents = self.contents();

                if ( contents.length ) {

                    contents.wrapAll( html );

                } else {

                    self.append( html );

            });

        wrap: function( html ) {

            var isFunction = jQuery.isFunction( html );

            return this.each(function(i) {

                jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );

        unwrap: function() {

            return this.parent().each(function() {

                if ( !jQuery.nodeName( this, "body" ) ) {

                    jQuery( this ).replaceWith( this.childNodes );

            }).end();

        append: function() {

            return this.domManip(arguments, true, function( elem ) {

                if ( this.nodeType === 1 ) {

                    this.appendChild( elem );

        prepend: function() {

                    this.insertBefore( elem, this.firstChild );

        before: function() {

            if ( this[0] && this[0].parentNode ) {

                return this.domManip(arguments, false, function( elem ) {

                    this.parentNode.insertBefore( elem, this );

            } else if ( arguments.length ) {

                var set = jQuery.clean( arguments );

                set.push.apply( set, this.toArray() );

                return this.pushStack( set, "before", arguments );

        after: function() {

                    this.parentNode.insertBefore( elem, this.nextSibling );

                var set = this.pushStack( this, "after", arguments );

                set.push.apply( set, jQuery.clean(arguments) );

                return set;

        // keepData is for internal use only--do not document

        remove: function( selector, keepData ) {

            for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {

                if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {

                    if ( !keepData && elem.nodeType === 1 ) {

                        jQuery.cleanData( elem.getElementsByTagName("*") );

                        jQuery.cleanData( [ elem ] );

                    if ( elem.parentNode ) {

                        elem.parentNode.removeChild( elem );

        empty: function() {

                // Remove element nodes and prevent memory leaks

                if ( elem.nodeType === 1 ) {

                    jQuery.cleanData( elem.getElementsByTagName("*") );

                // Remove any remaining nodes

                while ( elem.firstChild ) {

                    elem.removeChild( elem.firstChild );

        clone: function( dataAndEvents, deepDataAndEvents ) {

            dataAndEvents = dataAndEvents == null ? false : dataAndEvents;

            deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

            return this.map( function () {

                return jQuery.clone( this, dataAndEvents, deepDataAndEvents );

        html: function( value ) {

            if ( value === undefined ) {

                return this[0] && this[0].nodeType === 1 ?

                    this[0].innerHTML.replace(rinlinejQuery, "") :

                    null;

            // See if we can take a shortcut and just use innerHTML

            } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&

                (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&

                !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

                value = value.replace(rxhtmlTag, "<$1></$2>");

                try {

                    for ( var i = 0, l = this.length; i < l; i++ ) {

                        // Remove element nodes and prevent memory leaks

                        if ( this[i].nodeType === 1 ) {

                            jQuery.cleanData( this[i].getElementsByTagName("*") );

                            this[i].innerHTML = value;

                        }

                // If using innerHTML throws an exception, use the fallback method

                } catch(e) {

                    this.empty().append( value );

            } else if ( jQuery.isFunction( value ) ) {

                this.each(function(i){

                    self.html( value.call(this, i, self.html()) );

            } else {

                this.empty().append( value );

        replaceWith: function( value ) {

                // Make sure that the elements are removed from the DOM before they are inserted

                // this can help fix replacing a parent with child elements

                if ( jQuery.isFunction( value ) ) {

                    return this.each(function(i) {

                        var self = jQuery(this), old = self.html();

                        self.replaceWith( value.call( this, i, old ) );

                    });

                if ( typeof value !== "string" ) {

                    value = jQuery( value ).detach();

                return this.each(function() {

                    var next = this.nextSibling,

                        parent = this.parentNode;

                    jQuery( this ).remove();

                    if ( next ) {

                        jQuery(next).before( value );

                    } else {

                        jQuery(parent).append( value );

                return this.length ?

                    this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :

                    this;

        detach: function( selector ) {

            return this.remove( selector, true );

        domManip: function( args, table, callback ) {

            var results, first, fragment, parent,

                value = args[0],

                scripts = [];

            // We can't cloneNode fragments that contain checked, in WebKit

            if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {

                    jQuery(this).domManip( args, table, callback, true );

            if ( jQuery.isFunction(value) ) {

                    var self = jQuery(this);

                    args[0] = value.call(this, i, table ? self.html() : undefined);

                    self.domManip( args, table, callback );

                parent = value && value.parentNode;

                // If we're in a fragment, just use that instead of building a new one

                if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {

                    results = { fragment: parent };

                    results = jQuery.buildFragment( args, this, scripts );

                fragment = results.fragment;

                if ( fragment.childNodes.length === 1 ) {

                    first = fragment = fragment.firstChild;

                    first = fragment.firstChild;

                if ( first ) {

                    table = table && jQuery.nodeName( first, "tr" );

                    for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {

                        callback.call(

                            table ?

                                root(this[i], first) :

                                this[i],

                            // Make sure that we do not leak memory by inadvertently discarding

                            // the original fragment (which might have attached data) instead of

                            // using it; in addition, use the original fragment object for the last

                            // item instead of first because it can end up being emptied incorrectly

                            // in certain situations (Bug #8070).

                            // Fragments from the fragment cache must always be cloned and never used

                            // in place.

                            results.cacheable || ( l > 1 && i < lastIndex ) ?

                                jQuery.clone( fragment, true, true ) :

                                fragment

                        );

                if ( scripts.length ) {

                    jQuery.each( scripts, evalScript );

        }

    });

    jQuery.extend({

        clone: function( elem, dataAndEvents, deepDataAndEvents ) {

            var srcElements,

                destElements,

                i,

                // IE<=8 does not properly clone detached, unknown element nodes

                clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ?

                    elem.cloneNode( true ) :

                    shimCloneNode( elem );

            if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&

                    (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {

                // IE copies events bound via attachEvent when using cloneNode.

                // Calling detachEvent on the clone will also remove the events

                // from the original. In order to get around this, we use some

                // proprietary methods to clear the events. Thanks to MooTools

                // guys for this hotness.

                cloneFixAttributes( elem, clone );

                // Using Sizzle here is crazy slow, so we use getElementsByTagName instead

                srcElements = getAll( elem );

                destElements = getAll( clone );

                // Weird iteration because IE will replace the length property

                // with an element if you are cloning the body and one of the

                // elements on the page has a name or id of "length"

                for ( i = 0; srcElements[i]; ++i ) {

                    // Ensure that the destination node is not null; Fixes #9587

                    if ( destElements[i] ) {

                        cloneFixAttributes( srcElements[i], destElements[i] );

            // Copy the events from the original to the clone

            if ( dataAndEvents ) {

                cloneCopyEvent( elem, clone );

                if ( deepDataAndEvents ) {

                    srcElements = getAll( elem );

                    destElements = getAll( clone );

                    for ( i = 0; srcElements[i]; ++i ) {

                        cloneCopyEvent( srcElements[i], destElements[i] );

            srcElements = destElements = null;

            // Return the cloned set

            return clone;

        clean: function( elems, context, fragment, scripts ) {

            var checkScriptType;

            context = context || document;

            // !context.createElement fails in IE with an error but returns typeof 'object'

            if ( typeof context.createElement === "undefined" ) {

                context = context.ownerDocument || context[0] && context[0].ownerDocument || document;

            var ret = [], j;

            for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {

                if ( typeof elem === "number" ) {

                    elem += "";

                if ( !elem ) {

                    continue;

                // Convert html string into DOM nodes

                if ( typeof elem === "string" ) {

                    if ( !rhtml.test( elem ) ) {

                        elem = context.createTextNode( elem );

                        // Fix "XHTML"-style tags in all browsers

                        elem = elem.replace(rxhtmlTag, "<$1></$2>");

                        // Trim whitespace, otherwise indexOf won't work as expected

                        var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),

                            wrap = wrapMap[ tag ] || wrapMap._default,

                            depth = wrap[0],

                            div = context.createElement("div");

                        // Append wrapper element to unknown element safe doc fragment

                        if ( context === document ) {

                            // Use the fragment we've already created for this document

                            safeFragment.appendChild( div );

                        } else {

                            // Use a fragment created with the owner document

                            createSafeFragment( context ).appendChild( div );

                        // Go to html and back, then peel off extra wrappers

                        div.innerHTML = wrap[1] + elem + wrap[2];

                        // Move to the right depth

                        while ( depth-- ) {

                            div = div.lastChild;

                        // Remove IE's autoinserted <tbody> from table fragments

                        if ( !jQuery.support.tbody ) {

                            // String was a <table>, *may* have spurious <tbody>

                            var hasBody = rtbody.test(elem),

                                tbody = tag === "table" && !hasBody ?

                                    div.firstChild && div.firstChild.childNodes :

                                    // String was a bare <thead> or <tfoot>

                                    wrap[1] === "<table>" && !hasBody ?

                                        div.childNodes :

                                        [];

                            for ( j = tbody.length - 1; j >= 0 ; --j ) {

                                if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {

                                    tbody[ j ].parentNode.removeChild( tbody[ j ] );

                                }

                            }

                        // IE completely kills leading whitespace when innerHTML is used

                        if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {

                            div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );

                        elem = div.childNodes;

                // Resets defaultChecked for any radios and checkboxes

                // about to be appended to the DOM in IE 6/7 (#8060)

                var len;

                if ( !jQuery.support.appendChecked ) {

                    if ( elem[0] && typeof (len = elem.length) === "number" ) {

                        for ( j = 0; j < len; j++ ) {

                            findInputs( elem[j] );

                        findInputs( elem );

                if ( elem.nodeType ) {

                    ret.push( elem );

                    ret = jQuery.merge( ret, elem );

            if ( fragment ) {

                checkScriptType = function( elem ) {

                    return !elem.type || rscriptType.test( elem.type );

                };

                for ( i = 0; ret[i]; i++ ) {

                    if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {

                        scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );

                        if ( ret[i].nodeType === 1 ) {

                            var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );

                            ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );

                        fragment.appendChild( ret[i] );

            return ret;

        cleanData: function( elems ) {

            var data, id,

                cache = jQuery.cache,

                special = jQuery.event.special,

                deleteExpando = jQuery.support.deleteExpando;

                if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {

                id = elem[ jQuery.expando ];

                if ( id ) {

                    data = cache[ id ];

                    if ( data && data.events ) {

                        for ( var type in data.events ) {

                            if ( special[ type ] ) {

                                jQuery.event.remove( elem, type );

                            // This is a shortcut to avoid jQuery.event.remove's overhead

                            } else {

                                jQuery.removeEvent( elem, type, data.handle );

                        // Null the DOM reference to avoid IE6/7/8 leak (#7054)

                        if ( data.handle ) {

                            data.handle.elem = null;

                    if ( deleteExpando ) {

                        delete elem[ jQuery.expando ];

                    } else if ( elem.removeAttribute ) {

                        elem.removeAttribute( jQuery.expando );

                    delete cache[ id ];

    jQuery.buildFragment = function( args, nodes, scripts ) {

        var fragment, cacheable, cacheresults, doc,

        first = args[ 0 ];

        // nodes may contain either an explicit document object,

        // a jQuery collection or context object.

        // If nodes[0] contains a valid object to assign to doc

        if ( nodes && nodes[0] ) {

            doc = nodes[0].ownerDocument || nodes[0];

        // Ensure that an attr object doesn't incorrectly stand in as a document object

        // Chrome and Firefox seem to allow this to occur and will throw exception

        // Fixes #8950

        if ( !doc.createDocumentFragment ) {

            doc = document;

        // Only cache "small" (1/2 KB) HTML strings that are associated with the main document

        // Cloning options loses the selected state, so don't cache them

        // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment

        // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache

        // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501

        if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&

            first.charAt(0) === "<" && !rnocache.test( first ) &&

            (jQuery.support.checkClone || !rchecked.test( first )) &&

            (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {

            cacheable = true;

            cacheresults = jQuery.fragments[ first ];

            if ( cacheresults && cacheresults !== 1 ) {

                fragment = cacheresults;

        if ( !fragment ) {

            fragment = doc.createDocumentFragment();

            jQuery.clean( args, doc, fragment, scripts );

        if ( cacheable ) {

            jQuery.fragments[ first ] = cacheresults ? fragment : 1;

        return { fragment: fragment, cacheable: cacheable };

    };

    jQuery.fragments = {};

}

10、事件系統

我們将jQuery事件系統的相關代碼放在檔案jquery.extend.dom.js中。

function jQuery_extend_event(jQuery){

    var rformElems = /^(?:textarea|input|select)$/i,

    rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,

    rhoverHack = /\bhover(\.\S+)?\b/,

    rkeyEvent = /^key/,

    rmouseEvent = /^(?:mouse|contextmenu)|click/,

    rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,

    rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,

    quickParse = function( selector ) {

        var quick = rquickIs.exec( selector );

        if ( quick ) {

            //   0  1    2   3

            // [ _, tag, id, class ]

            quick[1] = ( quick[1] || "" ).toLowerCase();

            quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );

        return quick;

    },

    quickIs = function( elem, m ) {

        var attrs = elem.attributes || {};

        return (

            (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&

            (!m[2] || (attrs.id || {}).value === m[2]) &&

            (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))

        );

    hoverHack = function( events ) {

        return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );

    /*

     * Helper functions for managing events -- not part of the public interface.

     * Props to Dean Edwards' addEvent library for many of the ideas.

     */

    jQuery.event = {

        add: function( elem, types, handler, data, selector ) {

            var elemData, eventHandle, events,

                t, tns, type, namespaces, handleObj,

                handleObjIn, quick, handlers, special;

            // Don't attach events to noData or text/comment nodes (allow plain objects tho)

            if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {

                return;

            // Caller can pass in an object of custom data in lieu of the handler

            if ( handler.handler ) {

                handleObjIn = handler;

                handler = handleObjIn.handler;

            // Make sure that the handler has a unique ID, used to find/remove it later

            if ( !handler.guid ) {

                handler.guid = jQuery.guid++;

            // Init the element's event structure and main handler, if this is the first

            events = elemData.events;

            if ( !events ) {

                elemData.events = events = {};

            eventHandle = elemData.handle;

            if ( !eventHandle ) {

                elemData.handle = eventHandle = 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 !== "undefined" && (!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

                eventHandle.elem = elem;

            // Handle multiple events separated by a space

            // jQuery(...).bind("mouseover mouseout", fn);

            types = jQuery.trim( hoverHack(types) ).split( " " );

            for ( t = 0; t < types.length; t++ ) {

                tns = rtypenamespace.exec( types[t] ) || [];

                type = tns[1];

                namespaces = ( tns[2] || "" ).split( "." ).sort();

                // 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

                // handleObj is passed to all event handlers

                handleObj = jQuery.extend({

                    type: type,

                    origType: tns[1],

                    data: data,

                    handler: handler,

                    guid: handler.guid,

                    selector: selector,

                    quick: quickParse( selector ),

                    namespace: namespaces.join(".")

                }, handleObjIn );

                // Init the event handler queue if we're the first

                handlers = events[ type ];

                if ( !handlers ) {

                    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

                        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 );

                    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

            elem = null;

        global: {},

        // Detach an event or set of events from an element

        remove: function( elem, types, handler, selector, mappedTypes ) {

            var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),

                t, tns, type, origType, namespaces, origCount,

                j, events, special, handle, eventType, handleObj;

            if ( !elemData || !(events = elemData.events) ) {

            // Once for each type.namespace in types; type may be omitted

            types = jQuery.trim( hoverHack( types || "" ) ).split(" ");

                type = origType = tns[1];

                namespaces = tns[2];

                // Unbind all events (on this namespace, if provided) for the element

                if ( !type ) {

                    for ( type in events ) {

                        jQuery.event.remove( elem, type + types[ t ], handler, selector, true );

                type = ( selector? special.delegateType : special.bindType ) || type;

                eventType = events[ type ] || [];

                origCount = eventType.length;

                namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;

                // Remove matching events

                for ( j = 0; j < eventType.length; j++ ) {

                    handleObj = eventType[ j ];

                    if ( ( mappedTypes || origType === handleObj.origType ) &&

                         ( !handler || handler.guid === handleObj.guid ) &&

                         ( !namespaces || namespaces.test( handleObj.namespace ) ) &&

                         ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {

                        eventType.splice( j--, 1 );

                        if ( handleObj.selector ) {

                            eventType.delegateCount--;

                        if ( special.remove ) {

                            special.remove.call( elem, handleObj );

                // Remove generic event handler if we removed something and no more handlers exist

                // (avoids potential for endless recursion during removal of special event handlers)

                if ( eventType.length === 0 && origCount !== eventType.length ) {

                    if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {

                        jQuery.removeEvent( elem, type, elemData.handle );

                    delete events[ type ];

            // Remove the expando if it's no longer used

            if ( jQuery.isEmptyObject( events ) ) {

                handle = elemData.handle;

                if ( handle ) {

                    handle.elem = null;

                // removeData also checks for emptiness and clears the expando if empty

                // so use it instead of delete

                jQuery.removeData( elem, [ "events", "handle" ], true );

        // Events that are safe to short-circuit if no handlers are attached.

        // Native DOM events should not be added, they may have inline handlers.

        customEvent: {

            "getData": true,

            "setData": true,

            "changeData": true

        trigger: function( event, data, elem, onlyHandlers ) {

            // Don't do events on text and comment nodes

            if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {

            // Event object or event type

            var type = event.type || event,

                namespaces = [],

                cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;

            // focus/blur morphs to focusin/out; ensure we're not firing them right now

            if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {

            if ( type.indexOf( "!" ) >= 0 ) {

                // Exclusive events trigger only for the exact event (no namespaces)

                type = type.slice(0, -1);

                exclusive = true;

            if ( type.indexOf( "." ) >= 0 ) {

                // Namespaced trigger; create a regexp to match event type in handle()

                namespaces = type.split(".");

                type = namespaces.shift();

                namespaces.sort();

            if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {

                // No jQuery handlers for this event type, and it can't have inline handlers

            // Caller can pass in an Event, Object, or just an event type string

            event = typeof event === "object" ?

                // jQuery.Event object

                event[ jQuery.expando ] ? event :

                // Object literal

                new jQuery.Event( type, event ) :

                // Just the event type (string)

                new jQuery.Event( type );

            event.type = type;

            event.isTrigger = true;

            event.exclusive = exclusive;

            event.namespace = namespaces.join( "." );

            event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;

            ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";

            // Handle a global trigger

            if ( !elem ) {

                // TODO: Stop taunting the data cache; remove global events and always attach to document

                cache = jQuery.cache;

                for ( i in cache ) {

                    if ( cache[ i ].events && cache[ i ].events[ type ] ) {

                        jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );

            // 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 ? jQuery.makeArray( data ) : [];

            data.unshift( event );

            // Allow special events to draw outside the lines

            special = jQuery.event.special[ type ] || {};

            if ( special.trigger && special.trigger.apply( elem, data ) === false ) {

            // Determine event propagation path in advance, per W3C events spec (#9951)

            // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)

            eventPath = [[ elem, special.bindType || type ]];

            if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

                bubbleType = special.delegateType || type;

                cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;

                old = null;

                for ( ; cur; cur = cur.parentNode ) {

                    eventPath.push([ cur, bubbleType ]);

                    old = cur;

                // Only add window if we got to document (e.g., not plain obj or detached DOM)

                if ( old && old === elem.ownerDocument ) {

                    eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);

            // Fire handlers on the event path

            for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {

                cur = eventPath[i][0];

                event.type = eventPath[i][1];

                handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );

                    handle.apply( cur, data );

                // Note that this is a bare JS function and not a jQuery handler

                handle = ontype && cur[ ontype ];

                if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {

                    event.preventDefault();

            // If nobody prevented the default action, do it now

            if ( !onlyHandlers && !event.isDefaultPrevented() ) {

                if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&

                    !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {

                    // Call a native DOM method on the target with the same name name as the event.

                    // Can't use an .isFunction() check here because IE6/7 fails that test.

                    // Don't do default actions on window, that's where global variables be (#6170)

                    // IE<9 dies on focus/blur to hidden element (#1486)

                    if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {

                        // Don't re-trigger an onFOO event when we call its FOO() method

                        old = elem[ ontype ];

                        if ( old ) {

                            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;

                            elem[ ontype ] = old;

            return event.result;

        dispatch: function( event ) {

            // Make a writable jQuery.Event from the native event object

            event = jQuery.event.fix( event || window.event );

            var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),

                delegateCount = handlers.delegateCount,

                args = [].slice.call( arguments, 0 ),

                run_all = !event.exclusive && !event.namespace,

                handlerQueue = [],

                i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;

            // Use the fix-ed jQuery.Event rather than the (read-only) native event

            args[0] = event;

            event.delegateTarget = this;

            // Determine handlers that should run if there are delegated events

            // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)

            if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {

                // Pregenerate a single jQuery object for reuse with .is()

                jqcur = jQuery(this);

                jqcur.context = this.ownerDocument || this;

                for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {

                    selMatch = {};

                    matches = [];

                    jqcur[0] = cur;

                    for ( i = 0; i < delegateCount; i++ ) {

                        handleObj = handlers[ i ];

                        sel = handleObj.selector;

                        if ( selMatch[ sel ] === undefined ) {

                            selMatch[ sel ] = (

                                handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )

                            );

                        if ( selMatch[ sel ] ) {

                            matches.push( handleObj );

                    if ( matches.length ) {

                        handlerQueue.push({ elem: cur, matches: matches });

            // Add the remaining (directly-bound) handlers

            if ( handlers.length > delegateCount ) {

                handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });

            // Run delegates first; they may want to stop propagation beneath us

            for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {

                matched = handlerQueue[ i ];

                event.currentTarget = matched.elem;

                for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {

                    handleObj = matched.matches[ j ];

                    // Triggered event must either 1) be non-exclusive and have no namespace, or

                    // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).

                    if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {

                        event.data = handleObj.data;

                        event.handleObj = handleObj;

                        ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )

                                .apply( matched.elem, args );

                        if ( ret !== undefined ) {

                            event.result = ret;

                            if ( ret === false ) {

                                event.preventDefault();

                                event.stopPropagation();

        // Includes some event props shared by KeyEvent and MouseEvent

        // *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***

        props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),

        fixHooks: {},

        keyHooks: {

            props: "char charCode key keyCode".split(" "),

            filter: function( event, original ) {

                // Add which for key events

                if ( event.which == null ) {

                    event.which = original.charCode != null ? original.charCode : original.keyCode;

                return event;

        mouseHooks: {

            props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),

                var eventDoc, doc, body,

                    button = original.button,

                    fromElement = original.fromElement;

                // Calculate pageX/Y if missing and clientX/Y available

                if ( event.pageX == null && original.clientX != null ) {

                    eventDoc = event.target.ownerDocument || document;

                    doc = eventDoc.documentElement;

                    body = eventDoc.body;

                    event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );

                    event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );

                // Add relatedTarget, if necessary

                if ( !event.relatedTarget && fromElement ) {

                    event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;

                // Add which for click: 1 === left; 2 === middle; 3 === right

                // Note: button is not normalized, so don't use it

                if ( !event.which && button !== undefined ) {

                    event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );

        fix: function( event ) {

            if ( event[ jQuery.expando ] ) {

            // Create a writable copy of the event object and normalize some properties

            var i, prop,

                originalEvent = event,

                fixHook = jQuery.event.fixHooks[ event.type ] || {},

                copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

            event = jQuery.Event( originalEvent );

            for ( i = copy.length; i; ) {

                prop = copy[ --i ];

                event[ prop ] = originalEvent[ prop ];

            // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)

                event.target = originalEvent.srcElement || document;

            // Target should not be a text node (#504, Safari)

            if ( event.target.nodeType === 3 ) {

                event.target = event.target.parentNode;

            // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)

            if ( event.metaKey === undefined ) {

                event.metaKey = event.ctrlKey;

            return fixHook.filter? fixHook.filter( event, originalEvent ) : event;

        special: {

            ready: {

                // Make sure the ready event is setup

                setup: jQuery.bindReady

            },

            load: {

                // Prevent triggered p_w_picpath.load events from bubbling to window.load

                noBubble: true

            focus: {

                delegateType: "focusin"

            blur: {

                delegateType: "focusout"

            beforeunload: {

                setup: function( data, namespaces, eventHandle ) {

                    // We only want to do this special case on windows

                    if ( jQuery.isWindow( this ) ) {

                        this.onbeforeunload = eventHandle;

                },

                teardown: function( namespaces, eventHandle ) {

                    if ( this.onbeforeunload === eventHandle ) {

                        this.onbeforeunload = null;

        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( e, null, elem );

                jQuery.event.dispatch.call( elem, e );

            if ( e.isDefaultPrevented() ) {

                event.preventDefault();

    // Some plugins are using, but it's undocumented/deprecated and will be removed.

    // The 1.7 special event interface should provide all the hooks needed now.

    jQuery.event.handle = jQuery.event.dispatch;

    jQuery.removeEvent = document.removeEventListener ?

        function( elem, type, handle ) {

            if ( elem.removeEventListener ) {

                elem.removeEventListener( type, handle, false );

        } :

            if ( elem.detachEvent ) {

                elem.detachEvent( "on" + type, handle );

        };

    jQuery.Event = function( src, props ) {

        // Allow instantiation without the 'new' keyword

        if ( !(this instanceof jQuery.Event) ) {

            return new jQuery.Event( src, props );

        // Event object

        if ( src && src.type ) {

            this.originalEvent = src;

            this.type = src.type;

            // Events bubbling up the document may have been marked as prevented

            // by a handler lower down the tree; reflect the correct value.

            this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||

                src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;

        // Event type

        } else {

            this.type = src;

        // Put explicitly provided properties onto the event object

        if ( props ) {

            jQuery.extend( this, props );

        // Create a timestamp if incoming event doesn't have one

        this.timeStamp = src && src.timeStamp || jQuery.now();

        // Mark it as fixed

        this[ jQuery.expando ] = true;

    function returnFalse() {

        return false;

    }

    function returnTrue() {

        return true;

    // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding

    // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html

    jQuery.Event.prototype = {

        preventDefault: function() {

            this.isDefaultPrevented = returnTrue;

            var e = this.originalEvent;

            if ( !e ) {

            // if preventDefault exists run it on the original event

            if ( e.preventDefault ) {

                e.preventDefault();

            // otherwise set the returnValue property of the original event to false (IE)

                e.returnValue = false;

        stopPropagation: function() {

            this.isPropagationStopped = returnTrue;

            // if stopPropagation exists run it on the original event

            if ( e.stopPropagation ) {

                e.stopPropagation();

            // otherwise set the cancelBubble property of the original event to true (IE)

            e.cancelBubble = true;

        stopImmediatePropagation: function() {

            this.isImmediatePropagationStopped = returnTrue;

            this.stopPropagation();

        isDefaultPrevented: returnFalse,

        isPropagationStopped: returnFalse,

        isImmediatePropagationStopped: returnFalse

    // Create mouseenter/leave events using mouseover/out and event-time checks

    jQuery.each({

        mouseenter: "mouseover",

        mouseleave: "mouseout"

    }, function( orig, fix ) {

        jQuery.event.special[ orig ] = {

            delegateType: fix,

            bindType: fix,

            handle: function( event ) {

                var target = this,

                    related = event.relatedTarget,

                    handleObj = event.handleObj,

                    selector = handleObj.selector,

                    ret;

                // For mousenter/leave call the handler if related is outside the target.

                // NB: No relatedTarget if the mouse left/entered the browser window

                if ( !related || (related !== target && !jQuery.contains( target, related )) ) {

                    event.type = handleObj.origType;

                    ret = handleObj.handler.apply( this, arguments );

                    event.type = fix;

                return ret;

    // IE submit delegation

    if ( !jQuery.support.submitBubbles ) {

        jQuery.event.special.submit = {

            setup: function() {

                // Only need this for delegated form submit events

                if ( jQuery.nodeName( this, "form" ) ) {

                    return false;

                // Lazy-add a submit handler when a descendant form may potentially be submitted

                jQuery.event.add( this, "click._submit keypress._submit", function( e ) {

                    // Node name check avoids a VML-related crash in IE (#9807)

                    var elem = e.target,

                        form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;

                    if ( form && !form._submit_attached ) {

                        jQuery.event.add( form, "submit._submit", function( event ) {

                            // If form was submitted by the user, bubble the event up the tree

                            if ( this.parentNode && !event.isTrigger ) {

                                jQuery.event.simulate( "submit", this.parentNode, event, true );

                        });

                        form._submit_attached = true;

                // return undefined since we don't need an event listener

            teardown: function() {

                // Remove delegated handlers; cleanData eventually reaps submit handlers attached above

                jQuery.event.remove( this, "._submit" );

    // IE change delegation and checkbox/radio fix

    if ( !jQuery.support.changeBubbles ) {

        jQuery.event.special.change = {

                if ( rformElems.test( this.nodeName ) ) {

                    // IE doesn't fire change on a check/radio until blur; trigger it on click

                    // after a propertychange. Eat the blur-change in special.change.handle.

                    // This still fires onchange a second time for check/radio after blur.

                    if ( this.type === "checkbox" || this.type === "radio" ) {

                        jQuery.event.add( this, "propertychange._change", function( event ) {

                            if ( event.originalEvent.propertyName === "checked" ) {

                                this._just_changed = true;

                        jQuery.event.add( this, "click._change", function( event ) {

                            if ( this._just_changed && !event.isTrigger ) {

                                this._just_changed = false;

                                jQuery.event.simulate( "change", this, event, true );

                // Delegated event; lazy-add a change handler on descendant inputs

                jQuery.event.add( this, "beforeactivate._change", function( e ) {

                    var elem = e.target;

                    if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {

                        jQuery.event.add( elem, "change._change", function( event ) {

                            if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {

                                jQuery.event.simulate( "change", this.parentNode, event, true );

                        elem._change_attached = true;

                var elem = event.target;

                // Swallow native change events from checkbox/radio, we already triggered them above

                if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {

                    return event.handleObj.handler.apply( this, arguments );

                jQuery.event.remove( this, "._change" );

                return rformElems.test( this.nodeName );

    // Create "bubbling" focus and blur events

    if ( !jQuery.support.focusinBubbles ) {

        jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {

            // Attach a single capturing handler while someone wants 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 );

            };

        });

        on: function( types, selector, data, fn, /*INTERNAL*/ one ) {

            var origFn, type;

            // Types can be a map of types/handlers

            if ( typeof types === "object" ) {

                // ( types-Object, selector, data )

                if ( typeof selector !== "string" ) {

                    // ( types-Object, data )

                    data = selector;

                    selector = undefined;

                for ( type in types ) {

                    this.on( type, selector, data, types[ type ], one );

                return this;

            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;

                    // ( types, data, fn )

            if ( fn === false ) {

                fn = returnFalse;

            } else if ( !fn ) {

            if ( one === 1 ) {

                origFn = fn;

                fn = function( event ) {

                    // Can use an empty set, since event contains the info

                    jQuery().off( event );

                    return origFn.apply( this, arguments );

                // Use same guid so caller can remove using origFn

                fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );

            return this.each( function() {

                jQuery.event.add( this, types, fn, data, selector );

        one: function( types, selector, data, fn ) {

            return this.on.call( this, types, selector, data, fn, 1 );

        off: function( types, selector, fn ) {

            if ( types && types.preventDefault && types.handleObj ) {

                // ( event )  dispatched jQuery.Event

                var handleObj = types.handleObj;

                jQuery( types.delegateTarget ).off(

                    handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,

                    handleObj.selector,

                    handleObj.handler

                );

                // ( types-object [, selector] )

                for ( var type in types ) {

                    this.off( type, selector, types[ type ] );

            if ( selector === false || typeof selector === "function" ) {

                // ( types [, fn] )

                selector = undefined;

                jQuery.event.remove( this, types, fn, selector );

        bind: function( types, data, fn ) {

            return this.on( types, null, data, fn );

        unbind: function( types, fn ) {

            return this.off( types, null, fn );

        live: function( types, data, fn ) {

            jQuery( this.context ).on( types, this.selector, data, fn );

        die: function( types, fn ) {

            jQuery( this.context ).off( types, this.selector || "**", fn );

        delegate: function( selector, types, data, fn ) {

            return this.on( types, selector, data, fn );

        undelegate: function( selector, types, fn ) {

            // ( namespace ) or ( selector, types [, fn] )

            return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );

        trigger: function( type, data ) {

                jQuery.event.trigger( type, data, this );

        triggerHandler: function( type, data ) {

                return jQuery.event.trigger( type, data, this[0], true );

        toggle: function( fn ) {

            // Save reference to arguments for access in closure

            var args = arguments,

                guid = fn.guid || jQuery.guid++,

                i = 0,

                toggler = function( event ) {

                    // Figure out which function to execute

                    var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;

                    jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );

                    // Make sure that clicks stop

                    // and execute the function

                    return args[ lastToggle ].apply( this, arguments ) || false;

            // link all the functions, so any of them can unbind this click handler

            toggler.guid = guid;

            while ( i < args.length ) {

                args[ i++ ].guid = guid;

            return this.click( toggler );

        hover: function( fnOver, fnOut ) {

            return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );

    jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +

        "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +

        "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

        // Handle event binding

        jQuery.fn[ name ] = function( data, fn ) {

            if ( fn == null ) {

                fn = data;

                data = null;

            return arguments.length > 0 ?

                this.on( name, null, data, fn ) :

                this.trigger( name );

        if ( jQuery.attrFn ) {

            jQuery.attrFn[ name ] = true;

        if ( rkeyEvent.test( name ) ) {

            jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;

        if ( rmouseEvent.test( name ) ) {

            jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;

11、總結

最後,我們修改jquery.main.js檔案,調用以上各類初始化代碼。

// 整體結構

(function(window,undefined){

    var jQuery = (function(){

        var jQuery = function(selector, context){

            // 調用init方法建立jQuery對象

            return new jQuery.prototype.init(selector, context,rootjQuery);

        // 臨時變量

        var _$ = window.$;

        var _jQuery = window.jQuery;

        var rootjQuery;

        var toString = Object.prototype.toString;

        // 初始化jQuery對象

        jQuery_init(jQuery);

        jQuery_extend_defined(jQuery);

        jQuery_extend_core(jQuery,_$,_jQuery,toString);

        // All jQuery objects should point back to these

        rootjQuery = jQuery(document);

        return jQuery;

    })();

    // 初始化jQuery各個子產品

    jQuery_Sizzle(jQuery);

    jQuery_extend_sizzle(jQuery);

    jQuery_support(jQuery);

    jQuery_extend_attributes(jQuery);

    jQuery_extend_dom(jQuery);

    jQuery_extend_event(jQuery);

    window.$ = window.jQuery = jQuery;

})(window);

修改測試檔案,依次引入各檔案進行測試。

<!doctype html>

<html lang="en">

 <head>

  <meta charset="UTF-8">

  <script src="jquery.init.js"></script>

  <script src="jquery.extend.define.js"></script>

  <script src="jquery.extend.core.js"></script>

  <script src="jquery.sizzle.js"></script>

  <script src="jquery.extend.sizzle.js"></script>

  <script src="jquery.extend.support.js"></script>

  <script src="jquery.extend.attrs.js"></script>

  <script src="jquery.extend.dom.js"></script>

  <script src="jquery.extend.event.js"></script>

  <script src="jquery.main.js"></script>

  <title>jquery demo</title>

 </head>

 <body>

 <div id='tt'>我是一個div</div>

 <script>

    var s = $("#tt").text();

    alert(s);

    $("#tt").text("我更新了div的文本");

 </script> 

 </body>

</html>

第五節:JQuery架構源碼簡析(3)