天天看點

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

(續1)

5、選擇器Sizzle

我們把選擇器Sizzle實作的代碼放在檔案jquery.sizzle.js中。關于選擇器Sizzle的說明,請看《jQuery技術内幕:深入解析jQuery架構設計與實作原理》,這裡不再贅述了。

/*!

 * Sizzle CSS Selector Engine

 *  Copyright 2011, The Dojo Foundation

 *  Released under the MIT, BSD, and GPL Licenses.

 *  More information: http://sizzlejs.com/

 */

function jQuery_Sizzle(jQuery){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,

    expando = "sizcache" + (Math.random() + '').replace('.', ''),

    done = 0,

    toString = Object.prototype.toString,

    hasDuplicate = false,

    baseHasDuplicate = true,

    rBackslash = /\\/g,

    rReturn = /\r\n/g,

    rNonWord = /\W/;

// Here we check if the JavaScript engine is using some sort of

// optimization where it does not always call our comparision

// function. If that is the case, discard the hasDuplicate value.

//   Thus far that includes Google Chrome.

[0, 0].sort(function() {

    baseHasDuplicate = false;

    return 0;

});

var Sizzle = function( selector, context, results, seed ) {

    results = results || [];

    context = context || document;

    var origContext = context;

    if ( context.nodeType !== 1 && context.nodeType !== 9 ) {

        return [];

    }

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

        return results;

    var m, set, checkSet, extra, ret, cur, pop, i,

        prune = true,

        contextXML = Sizzle.isXML( context ),

        parts = [],

        soFar = selector;

    // Reset the position of the chunker regexp (start from head)

    do {

        chunker.exec( "" );

        m = chunker.exec( soFar );

        if ( m ) {

            soFar = m[3];

            parts.push( m[1] );

            if ( m[2] ) {

                extra = m[3];

                break;

            }

        }

    } while ( m );

    if ( parts.length > 1 && origPOS.exec( selector ) ) {

        if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {

            set = posProcess( parts[0] + parts[1], context, seed );

        } else {

            set = Expr.relative[ parts[0] ] ?

                [ context ] :

                Sizzle( parts.shift(), context );

            while ( parts.length ) {

                selector = parts.shift();

                if ( Expr.relative[ selector ] ) {

                    selector += parts.shift();

                }

                set = posProcess( selector, set, seed );

    } else {

        // Take a shortcut and set the context if the root selector is an ID

        // (but not if it'll be faster if the inner selector is an ID)

        if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&

                Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {

            ret = Sizzle.find( parts.shift(), context, contextXML );

            context = ret.expr ?

                Sizzle.filter( ret.expr, ret.set )[0] :

                ret.set[0];

        if ( context ) {

            ret = seed ?

                { expr: parts.pop(), set: makeArray(seed) } :

                Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );

            set = ret.expr ?

                Sizzle.filter( ret.expr, ret.set ) :

                ret.set;

            if ( parts.length > 0 ) {

                checkSet = makeArray( set );

            } else {

                prune = false;

                cur = parts.pop();

                pop = cur;

                if ( !Expr.relative[ cur ] ) {

                    cur = "";

                } else {

                    pop = parts.pop();

                if ( pop == null ) {

                    pop = context;

                Expr.relative[ cur ]( checkSet, pop, contextXML );

            checkSet = parts = [];

    if ( !checkSet ) {

        checkSet = set;

        Sizzle.error( cur || selector );

    if ( toString.call(checkSet) === "[object Array]" ) {

        if ( !prune ) {

            results.push.apply( results, checkSet );

        } else if ( context && context.nodeType === 1 ) {

            for ( i = 0; checkSet[i] != null; i++ ) {

                if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {

                    results.push( set[i] );

                if ( checkSet[i] && checkSet[i].nodeType === 1 ) {

        makeArray( checkSet, results );

    if ( extra ) {

        Sizzle( extra, origContext, results, seed );

        Sizzle.uniqueSort( results );

    return results;

};

Sizzle.uniqueSort = function( results ) {

    if ( sortOrder ) {

        hasDuplicate = baseHasDuplicate;

        results.sort( sortOrder );

        if ( hasDuplicate ) {

            for ( var i = 1; i < results.length; i++ ) {

                if ( results[i] === results[ i - 1 ] ) {

                    results.splice( i--, 1 );

Sizzle.matches = function( expr, set ) {

    return Sizzle( expr, null, null, set );

Sizzle.matchesSelector = function( node, expr ) {

    return Sizzle( expr, null, null, [node] ).length > 0;

Sizzle.find = function( expr, context, isXML ) {

    var set, i, len, match, type, left;

    if ( !expr ) {

    for ( i = 0, len = Expr.order.length; i < len; i++ ) {

        type = Expr.order[i];

        if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {

            left = match[1];

            match.splice( 1, 1 );

            if ( left.substr( left.length - 1 ) !== "\\" ) {

                match[1] = (match[1] || "").replace( rBackslash, "" );

                set = Expr.find[ type ]( match, context, isXML );

                if ( set != null ) {

                    expr = expr.replace( Expr.match[ type ], "" );

                    break;

    if ( !set ) {

        set = typeof context.getElementsByTagName !== "undefined" ?

            context.getElementsByTagName( "*" ) :

            [];

    return { set: set, expr: expr };

Sizzle.filter = function( expr, set, inplace, not ) {

    var match, anyFound,

        type, found, item, filter, left,

        i, pass,

        old = expr,

        result = [],

        curLoop = set,

        isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );

    while ( expr && set.length ) {

        for ( type in Expr.filter ) {

            if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {

                filter = Expr.filter[ type ];

                left = match[1];

                anyFound = false;

                match.splice(1,1);

                if ( left.substr( left.length - 1 ) === "\\" ) {

                    continue;

                if ( curLoop === result ) {

                    result = [];

                if ( Expr.preFilter[ type ] ) {

                    match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

                    if ( !match ) {

                        anyFound = found = true;

                    } else if ( match === true ) {

                        continue;

                    }

                if ( match ) {

                    for ( i = 0; (item = curLoop[i]) != null; i++ ) {

                        if ( item ) {

                            found = filter( item, match, i, curLoop );

                            pass = not ^ found;

                            if ( inplace && found != null ) {

                                if ( pass ) {

                                    anyFound = true;

                                } else {

                                    curLoop[i] = false;

                                }

                            } else if ( pass ) {

                                result.push( item );

                                anyFound = true;

                            }

                        }

                if ( found !== undefined ) {

                    if ( !inplace ) {

                        curLoop = result;

                    if ( !anyFound ) {

                        return [];

        // Improper expression

        if ( expr === old ) {

            if ( anyFound == null ) {

                Sizzle.error( expr );

        old = expr;

    return curLoop;

Sizzle.error = function( msg ) {

    throw new Error( "Syntax error, unrecognized expression: " + msg );

/**

 * Utility function for retreiving the text value of an array of DOM nodes

 * @param {Array|Element} elem

var getText = Sizzle.getText = function( elem ) {

    var i, node,

        nodeType = elem.nodeType,

        ret = "";

    if ( nodeType ) {

        if ( nodeType === 1 || nodeType === 9 ) {

            // Use textContent || innerText for elements

            if ( typeof elem.textContent === 'string' ) {

                return elem.textContent;

            } else if ( typeof elem.innerText === 'string' ) {

                // Replace IE's carriage returns

                return elem.innerText.replace( rReturn, '' );

                // Traverse it's children

                for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {

                    ret += getText( elem );

        } else if ( nodeType === 3 || nodeType === 4 ) {

            return elem.nodeValue;

        // If no nodeType, this is expected to be an array

        for ( i = 0; (node = elem[i]); i++ ) {

            // Do not traverse comment nodes

            if ( node.nodeType !== 8 ) {

                ret += getText( node );

    return ret;

var Expr = Sizzle.selectors = {

    order: [ "ID", "NAME", "TAG" ],

    match: {

        ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,

        CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,

        NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,

        ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,

        TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,

        CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,

        POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,

        PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/

    },

    leftMatch: {},

    attrMap: {

        "class": "className",

        "for": "htmlFor"

    attrHandle: {

        href: function( elem ) {

            return elem.getAttribute( "href" );

        },

        type: function( elem ) {

            return elem.getAttribute( "type" );

    relative: {

        "+": function(checkSet, part){

            var isPartStr = typeof part === "string",

                isTag = isPartStr && !rNonWord.test( part ),

                isPartStrNotTag = isPartStr && !isTag;

            if ( isTag ) {

                part = part.toLowerCase();

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

                if ( (elem = checkSet[i]) ) {

                    while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

                    checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?

                        elem || false :

                        elem === part;

            if ( isPartStrNotTag ) {

                Sizzle.filter( part, checkSet, true );

        ">": function( checkSet, part ) {

            var elem,

                isPartStr = typeof part === "string",

                i = 0,

                l = checkSet.length;

            if ( isPartStr && !rNonWord.test( part ) ) {

                for ( ; i < l; i++ ) {

                    elem = checkSet[i];

                    if ( elem ) {

                        var parent = elem.parentNode;

                        checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;

                        checkSet[i] = isPartStr ?

                            elem.parentNode :

                            elem.parentNode === part;

                if ( isPartStr ) {

                    Sizzle.filter( part, checkSet, true );

        "": function(checkSet, part, isXML){

            var nodeCheck,

                doneName = done++,

                checkFn = dirCheck;

            if ( typeof part === "string" && !rNonWord.test( part ) ) {

                nodeCheck = part;

                checkFn = dirNodeCheck;

            checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );

        "~": function( checkSet, part, isXML ) {

            checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );

    find: {

        ID: function( match, context, isXML ) {

            if ( typeof context.getElementById !== "undefined" && !isXML ) {

                var m = context.getElementById(match[1]);

                // Check parentNode to catch when Blackberry 4.6 returns

                // nodes that are no longer in the document #6963

                return m && m.parentNode ? [m] : [];

        NAME: function( match, context ) {

            if ( typeof context.getElementsByName !== "undefined" ) {

                var ret = [],

                    results = context.getElementsByName( match[1] );

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

                    if ( results[i].getAttribute("name") === match[1] ) {

                        ret.push( results[i] );

                return ret.length === 0 ? null : ret;

        TAG: function( match, context ) {

            if ( typeof context.getElementsByTagName !== "undefined" ) {

                return context.getElementsByTagName( match[1] );

    preFilter: {

        CLASS: function( match, curLoop, inplace, result, not, isXML ) {

            match = " " + match[1].replace( rBackslash, "" ) + " ";

            if ( isXML ) {

                return match;

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

                if ( elem ) {

                    if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {

                        if ( !inplace ) {

                            result.push( elem );

                    } else if ( inplace ) {

                        curLoop[i] = false;

            return false;

        ID: function( match ) {

            return match[1].replace( rBackslash, "" );

        TAG: function( match, curLoop ) {

            return match[1].replace( rBackslash, "" ).toLowerCase();

        CHILD: function( match ) {

            if ( match[1] === "nth" ) {

                if ( !match[2] ) {

                    Sizzle.error( match[0] );

                match[2] = match[2].replace(/^\+|\s*/g, '');

                // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'

                var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(

                    match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||

                    !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

                // calculate the numbers (first)n+(last) including if they are negative

                match[2] = (test[1] + (test[2] || 1)) - 0;

                match[3] = test[3] - 0;

            else if ( match[2] ) {

                Sizzle.error( match[0] );

            // TODO: Move to normal caching system

            match[0] = done++;

            return match;

        ATTR: function( match, curLoop, inplace, result, not, isXML ) {

            var name = match[1] = match[1].replace( rBackslash, "" );

            if ( !isXML && Expr.attrMap[name] ) {

                match[1] = Expr.attrMap[name];

            // Handle if an un-quoted value was used

            match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );

            if ( match[2] === "~=" ) {

                match[4] = " " + match[4] + " ";

        PSEUDO: function( match, curLoop, inplace, result, not ) {

            if ( match[1] === "not" ) {

                // If we're dealing with a complex expression, or a simple one

                if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {

                    match[3] = Sizzle(match[3], null, null, curLoop);

                    var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);

                        result.push.apply( result, ret );

                    return false;

            } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {

                return true;

        POS: function( match ) {

            match.unshift( true );

    filters: {

        enabled: function( elem ) {

            return elem.disabled === false && elem.type !== "hidden";

        disabled: function( elem ) {

            return elem.disabled === true;

        checked: function( elem ) {

            return elem.checked === true;

        selected: function( elem ) {

            // Accessing this property makes selected-by-default

            // options in Safari work properly

            if ( elem.parentNode ) {

                elem.parentNode.selectedIndex;

            return elem.selected === true;

        parent: function( elem ) {

            return !!elem.firstChild;

        empty: function( elem ) {

            return !elem.firstChild;

        has: function( elem, i, match ) {

            return !!Sizzle( match[3], elem ).length;

        header: function( elem ) {

            return (/h\d/i).test( elem.nodeName );

        text: function( elem ) {

            var attr = elem.getAttribute( "type" ), type = elem.type;

            // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)

            // use getAttribute instead to test this case

            return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );

        radio: function( elem ) {

            return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;

        checkbox: function( elem ) {

            return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;

        file: function( elem ) {

            return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;

        password: function( elem ) {

            return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;

        submit: function( elem ) {

            var name = elem.nodeName.toLowerCase();

            return (name === "input" || name === "button") && "submit" === elem.type;

        p_w_picpath: function( elem ) {

            return elem.nodeName.toLowerCase() === "input" && "p_w_picpath" === elem.type;

        reset: function( elem ) {

            return (name === "input" || name === "button") && "reset" === elem.type;

        button: function( elem ) {

            return name === "input" && "button" === elem.type || name === "button";

        input: function( elem ) {

            return (/input|select|textarea|button/i).test( elem.nodeName );

        focus: function( elem ) {

            return elem === elem.ownerDocument.activeElement;

    setFilters: {

        first: function( elem, i ) {

            return i === 0;

        last: function( elem, i, match, array ) {

            return i === array.length - 1;

        even: function( elem, i ) {

            return i % 2 === 0;

        odd: function( elem, i ) {

            return i % 2 === 1;

        lt: function( elem, i, match ) {

            return i < match[3] - 0;

        gt: function( elem, i, match ) {

            return i > match[3] - 0;

        nth: function( elem, i, match ) {

            return match[3] - 0 === i;

        eq: function( elem, i, match ) {

    filter: {

        PSEUDO: function( elem, match, i, array ) {

            var name = match[1],

                filter = Expr.filters[ name ];

            if ( filter ) {

                return filter( elem, i, match, array );

            } else if ( name === "contains" ) {

                return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;

            } else if ( name === "not" ) {

                var not = match[3];

                for ( var j = 0, l = not.length; j < l; j++ ) {

                    if ( not[j] === elem ) {

                        return false;

                Sizzle.error( name );

        CHILD: function( elem, match ) {

            var first, last,

                doneName, parent, cache,

                count, diff,

                type = match[1],

                node = elem;

            switch ( type ) {

                case "only":

                case "first":

                    while ( (node = node.previousSibling) )     {

                        if ( node.nodeType === 1 ) {

                            return false;

                    if ( type === "first" ) {

                        return true;

                    node = elem;

                case "last":

                    while ( (node = node.nextSibling) )     {

                    return true;

                case "nth":

                    first = match[2];

                    last = match[3];

                    if ( first === 1 && last === 0 ) {

                    doneName = match[0];

                    parent = elem.parentNode;

                    if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {

                        count = 0;

                        for ( node = parent.firstChild; node; node = node.nextSibling ) {

                            if ( node.nodeType === 1 ) {

                                node.nodeIndex = ++count;

                        parent[ expando ] = doneName;

                    diff = elem.nodeIndex - last;

                    if ( first === 0 ) {

                        return diff === 0;

                    } else {

                        return ( diff % first === 0 && diff / first >= 0 );

        ID: function( elem, match ) {

            return elem.nodeType === 1 && elem.getAttribute("id") === match;

        TAG: function( elem, match ) {

            return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;

        CLASS: function( elem, match ) {

            return (" " + (elem.className || elem.getAttribute("class")) + " ")

                .indexOf( match ) > -1;

        ATTR: function( elem, match ) {

                result = Sizzle.attr ?

                    Sizzle.attr( elem, name ) :

                    Expr.attrHandle[ name ] ?

                    Expr.attrHandle[ name ]( elem ) :

                    elem[ name ] != null ?

                        elem[ name ] :

                        elem.getAttribute( name ),

                value = result + "",

                type = match[2],

                check = match[4];

            return result == null ?

                type === "!=" :

                !type && Sizzle.attr ?

                result != null :

                type === "=" ?

                value === check :

                type === "*=" ?

                value.indexOf(check) >= 0 :

                type === "~=" ?

                (" " + value + " ").indexOf(check) >= 0 :

                !check ?

                value && result !== false :

                type === "!=" ?

                value !== check :

                type === "^=" ?

                value.indexOf(check) === 0 :

                type === "$=" ?

                value.substr(value.length - check.length) === check :

                type === "|=" ?

                value === check || value.substr(0, check.length + 1) === check + "-" :

                false;

        POS: function( elem, match, i, array ) {

            var name = match[2],

                filter = Expr.setFilters[ name ];

var origPOS = Expr.match.POS,

    fescape = function(all, num){

        return "\\" + (num - 0 + 1);

    };

for ( var type in Expr.match ) {

    Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );

    Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );

}

var makeArray = function( array, results ) {

    array = Array.prototype.slice.call( array, 0 );

    if ( results ) {

        results.push.apply( results, array );

    return array;

// Perform a simple check to determine if the browser is capable of

// converting a NodeList to an array using builtin methods.

// Also verifies that the returned array holds DOM nodes

// (which is not the case in the Blackberry browser)

try {

    Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;

// Provide a fallback method if it does not work

} catch( e ) {

    makeArray = function( array, results ) {

        var i = 0,

            ret = results || [];

        if ( toString.call(array) === "[object Array]" ) {

            Array.prototype.push.apply( ret, array );

            if ( typeof array.length === "number" ) {

                for ( var l = array.length; i < l; i++ ) {

                    ret.push( array[i] );

                for ( ; array[i]; i++ ) {

        return ret;

var sortOrder, siblingCheck;

if ( document.documentElement.compareDocumentPosition ) {

    sortOrder = function( a, b ) {

        if ( a === b ) {

            hasDuplicate = true;

            return 0;

        if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {

            return a.compareDocumentPosition ? -1 : 1;

        return a.compareDocumentPosition(b) & 4 ? -1 : 1;

} else {

        // The nodes are identical, we can exit early

        // Fallback to using sourceIndex (in IE) if it's available on both nodes

        } else if ( a.sourceIndex && b.sourceIndex ) {

            return a.sourceIndex - b.sourceIndex;

        var al, bl,

            ap = [],

            bp = [],

            aup = a.parentNode,

            bup = b.parentNode,

            cur = aup;

        // If the nodes are siblings (or identical) we can do a quick check

        if ( aup === bup ) {

            return siblingCheck( a, b );

        // If no parents were found then the nodes are disconnected

        } else if ( !aup ) {

            return -1;

        } else if ( !bup ) {

            return 1;

        // Otherwise they're somewhere else in the tree so we need

        // to build up a full list of the parentNodes for comparison

        while ( cur ) {

            ap.unshift( cur );

            cur = cur.parentNode;

        cur = bup;

            bp.unshift( cur );

        al = ap.length;

        bl = bp.length;

        // Start walking down the tree looking for a discrepancy

        for ( var i = 0; i < al && i < bl; i++ ) {

            if ( ap[i] !== bp[i] ) {

                return siblingCheck( ap[i], bp[i] );

        // We ended someplace up the tree so do a sibling check

        return i === al ?

            siblingCheck( a, bp[i], -1 ) :

            siblingCheck( ap[i], b, 1 );

    siblingCheck = function( a, b, ret ) {

            return ret;

        var cur = a.nextSibling;

            if ( cur === b ) {

                return -1;

            cur = cur.nextSibling;

        return 1;

// Check to see if the browser returns elements by name when

// querying by getElementById (and provide a workaround)

(function(){

    // We're going to inject a fake input element with a specified name

    var form = document.createElement("div"),

        id = "script" + (new Date()).getTime(),

        root = document.documentElement;

    form.innerHTML = "<a name='" + id + "'/>";

    // Inject it into the root element, check its status, and remove it quickly

    root.insertBefore( form, root.firstChild );

    // The workaround has to do additional checks after a getElementById

    // Which slows things down for other browsers (hence the branching)

    if ( document.getElementById( id ) ) {

        Expr.find.ID = function( match, context, isXML ) {

                return m ?

                    m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?

                        [m] :

                        undefined :

                    [];

        };

        Expr.filter.ID = function( elem, match ) {

            var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");

            return elem.nodeType === 1 && node && node.nodeValue === match;

    root.removeChild( form );

    // release memory in IE

    root = form = null;

})();

    // Check to see if the browser returns only elements

    // when doing getElementsByTagName("*")

    // Create a fake element

    var div = document.createElement("div");

    div.appendChild( document.createComment("") );

    // Make sure no comments are found

    if ( div.getElementsByTagName("*").length > 0 ) {

        Expr.find.TAG = function( match, context ) {

            var results = context.getElementsByTagName( match[1] );

            // Filter out possible comments

            if ( match[1] === "*" ) {

                var tmp = [];

                for ( var i = 0; results[i]; i++ ) {

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

                        tmp.push( results[i] );

                results = tmp;

            return results;

    // Check to see if an attribute returns normalized href attributes

    div.innerHTML = "<a href='#'></a>";

    if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&

            div.firstChild.getAttribute("href") !== "#" ) {

        Expr.attrHandle.href = function( elem ) {

            return elem.getAttribute( "href", 2 );

    div = null;

if ( document.querySelectorAll ) {

    (function(){

        var oldSizzle = Sizzle,

            div = document.createElement("div"),

            id = "__sizzle__";

        div.innerHTML = "<p class='TEST'></p>";

        // Safari can't handle uppercase or unicode characters when

        // in quirks mode.

        if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {

            return;

        Sizzle = function( query, context, extra, seed ) {

            context = context || document;

            // Only use querySelectorAll on non-XML documents

            // (ID selectors don't work in non-HTML documents)

            if ( !seed && !Sizzle.isXML(context) ) {

                // See if we find a selector to speed up

                var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );

                if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {

                    // Speed-up: Sizzle("TAG")

                    if ( match[1] ) {

                        return makeArray( context.getElementsByTagName( query ), extra );

                    // Speed-up: Sizzle(".CLASS")

                    } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {

                        return makeArray( context.getElementsByClassName( match[2] ), extra );

                if ( context.nodeType === 9 ) {

                    // Speed-up: Sizzle("body")

                    // The body element only exists once, optimize finding it

                    if ( query === "body" && context.body ) {

                        return makeArray( [ context.body ], extra );

                    // Speed-up: Sizzle("#ID")

                    } else if ( match && match[3] ) {

                        var elem = context.getElementById( match[3] );

                        // Check parentNode to catch when Blackberry 4.6 returns

                        // nodes that are no longer in the document #6963

                        if ( elem && elem.parentNode ) {

                            // Handle the case where IE and Opera return items

                            // by name instead of ID

                            if ( elem.id === match[3] ) {

                                return makeArray( [ elem ], extra );

                        } else {

                            return makeArray( [], extra );

                    try {

                        return makeArray( context.querySelectorAll(query), extra );

                    } catch(qsaError) {}

                // qSA works strangely on Element-rooted queries

                // We can work around this by specifying an extra ID on the root

                // and working up from there (Thanks to Andrew Dupont for the technique)

                // IE 8 doesn't work on object elements

                } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {

                    var oldContext = context,

                        old = context.getAttribute( "id" ),

                        nid = old || id,

                        hasParent = context.parentNode,

                        relativeHierarchySelector = /^\s*[+~]/.test( query );

                    if ( !old ) {

                        context.setAttribute( "id", nid );

                        nid = nid.replace( /'/g, "\\$&" );

                    if ( relativeHierarchySelector && hasParent ) {

                        context = context.parentNode;

                        if ( !relativeHierarchySelector || hasParent ) {

                            return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );

                    } catch(pseudoError) {

                    } finally {

                        if ( !old ) {

                            oldContext.removeAttribute( "id" );

            return oldSizzle(query, context, extra, seed);

        for ( var prop in oldSizzle ) {

            Sizzle[ prop ] = oldSizzle[ prop ];

        // release memory in IE

        div = null;

    })();

    var html = document.documentElement,

        matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;

    if ( matches ) {

        // Check to see if it's possible to do matchesSelector

        // on a disconnected node (IE 9 fails this)

        var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),

            pseudoWorks = false;

        try {

            // This should fail with an exception

            // Gecko does not error, returns false instead

            matches.call( document.documentElement, "[test!='']:sizzle" );

        } catch( pseudoError ) {

            pseudoWorks = true;

        Sizzle.matchesSelector = function( node, expr ) {

            // Make sure that attribute selectors are quoted

            expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");

            if ( !Sizzle.isXML( node ) ) {

                try {

                    if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {

                        var ret = matches.call( node, expr );

                        // IE 9's matchesSelector returns false on disconnected nodes

                        if ( ret || !disconnectedMatch ||

                                // As well, disconnected nodes are said to be in a document

                                // fragment in IE 9, so check for that

                                node.document && node.document.nodeType !== 11 ) {

                            return ret;

                } catch(e) {}

            return Sizzle(expr, null, null, [node]).length > 0;

    div.innerHTML = "<div class='test e'></div><div class='test'></div>";

    // Opera can't find a second classname (in 9.6)

    // Also, make sure that getElementsByClassName actually exists

    if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {

        return;

    // Safari caches class attributes, doesn't catch changes (in 3.2)

    div.lastChild.className = "e";

    if ( div.getElementsByClassName("e").length === 1 ) {

    Expr.order.splice(1, 0, "CLASS");

    Expr.find.CLASS = function( match, context, isXML ) {

        if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {

            return context.getElementsByClassName(match[1]);

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {

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

        var elem = checkSet[i];

        if ( elem ) {

            var match = false;

            elem = elem[dir];

            while ( elem ) {

                if ( elem[ expando ] === doneName ) {

                    match = checkSet[elem.sizset];

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

                    elem[ expando ] = doneName;

                    elem.sizset = i;

                if ( elem.nodeName.toLowerCase() === cur ) {

                    match = elem;

                elem = elem[dir];

            checkSet[i] = match;

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {

                if ( elem.nodeType === 1 ) {

                    if ( !isXML ) {

                        elem[ expando ] = doneName;

                        elem.sizset = i;

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

                        if ( elem === cur ) {

                            match = true;

                            break;

                    } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {

                        match = elem;

                        break;

if ( document.documentElement.contains ) {

    Sizzle.contains = function( a, b ) {

        return a !== b && (a.contains ? a.contains(b) : true);

} else if ( document.documentElement.compareDocumentPosition ) {

        return !!(a.compareDocumentPosition(b) & 16);

    Sizzle.contains = function() {

        return false;

Sizzle.isXML = function( elem ) {

    // documentElement is verified for cases where it doesn't yet exist

    // (such as loading iframes in IE - #4833)

    var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;

    return documentElement ? documentElement.nodeName !== "HTML" : false;

var posProcess = function( selector, context, seed ) {

    var match,

        tmpSet = [],

        later = "",

        root = context.nodeType ? [context] : context;

    // Position selectors must be done after the filter

    // And so must :not(positional) so we move all PSEUDOs to the end

    while ( (match = Expr.match.PSEUDO.exec( selector )) ) {

        later += match[0];

        selector = selector.replace( Expr.match.PSEUDO, "" );

    selector = Expr.relative[selector] ? selector + "*" : selector;

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

        Sizzle( selector, root[i], tmpSet, seed );

    return Sizzle.filter( later, tmpSet );

// EXPOSE

// Override sizzle attribute retrieval

Sizzle.attr = jQuery.attr;

Sizzle.selectors.attrMap = {};

jQuery.find = Sizzle;

jQuery.expr = Sizzle.selectors;

jQuery.expr[":"] = jQuery.expr.filters;

jQuery.unique = Sizzle.uniqueSort;

jQuery.text = Sizzle.getText;

jQuery.isXMLDoc = Sizzle.isXML;

jQuery.contains = Sizzle.contains;

6、選擇器擴充方法

我們把有關選擇器的相關方法代碼放在檔案jquery.extend.sizzle.js中。

function jQuery_extend_sizzle(jQuery){

    var runtil = /Until$/,

        rparentsprev = /^(?:parents|prevUntil|prevAll)/,

        // Note: This RegExp should be improved, or likely pulled from Sizzle

        rmultiselector = /,/,

        isSimple = /^.[^:#\[\.,]*$/,

        slice = Array.prototype.slice,

        POS = jQuery.expr.match.POS,

        // methods guaranteed to produce a unique set when starting from a unique set

        guaranteedUnique = {

            children: true,

            contents: true,

            next: true,

            prev: true

    jQuery.fn.extend({

        find: function( selector ) {

            var self = this,

                i, l;

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

                return jQuery( selector ).filter(function() {

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

                        if ( jQuery.contains( self[ i ], this ) ) {

                            return true;

                });

            var ret = this.pushStack( "", "find", selector ),

                length, n, r;

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

                length = ret.length;

                jQuery.find( selector, this[i], ret );

                if ( i > 0 ) {

                    // Make sure that the results are unique

                    for ( n = length; n < ret.length; n++ ) {

                        for ( r = 0; r < length; r++ ) {

                            if ( ret[r] === ret[n] ) {

                                ret.splice(n--, 1);

                                break;

        has: function( target ) {

            var targets = jQuery( target );

            return this.filter(function() {

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

                    if ( jQuery.contains( this, targets[i] ) ) {

            });

        not: function( selector ) {

            return this.pushStack( winnow(this, selector, false), "not", selector);

        filter: function( selector ) {

            return this.pushStack( winnow(this, selector, true), "filter", selector );

        is: function( selector ) {

            return !!selector && (

                typeof selector === "string" ?

                    // If this is a positional selector, check membership in the returned set

                    // so $("p:first").is("p:last") won't return true for a doc with two "p".

                    POS.test( selector ) ?

                        jQuery( selector, this.context ).index( this[0] ) >= 0 :

                        jQuery.filter( selector, this ).length > 0 :

                    this.filter( selector ).length > 0 );

        closest: function( selectors, context ) {

            var ret = [], i, l, cur = this[0];

            // Array (deprecated as of jQuery 1.7)

            if ( jQuery.isArray( selectors ) ) {

                var level = 1;

                while ( cur && cur.ownerDocument && cur !== context ) {

                    for ( i = 0; i < selectors.length; i++ ) {

                        if ( jQuery( cur ).is( selectors[ i ] ) ) {

                            ret.push({ selector: selectors[ i ], elem: cur, level: level });

                    cur = cur.parentNode;

                    level++;

                return ret;

            // String

            var pos = POS.test( selectors ) || typeof selectors !== "string" ?

                    jQuery( selectors, context || this.context ) :

                    0;

                cur = this[i];

                while ( cur ) {

                    if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {

                        ret.push( cur );

                        cur = cur.parentNode;

                        if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {

            ret = ret.length > 1 ? jQuery.unique( ret ) : ret;

            return this.pushStack( ret, "closest", selectors );

        // Determine the position of an element within

        // the matched set of elements

        index: function( elem ) {

            // No argument, return index in parent

            if ( !elem ) {

                return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;

            // index in selector

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

                return jQuery.inArray( this[0], jQuery( elem ) );

            // Locate the position of the desired element

            return jQuery.inArray(

                // If it receives a jQuery object, the first element is used

                elem.jquery ? elem[0] : elem, this );

        add: function( selector, context ) {

            var set = typeof selector === "string" ?

                    jQuery( selector, context ) :

                    jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),

                all = jQuery.merge( this.get(), set );

            return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?

                all :

                jQuery.unique( all ) );

        andSelf: function() {

            return this.add( this.prevObject );

    });

7、浏覽器功能測試

我們将浏覽器功能測試support的相關方法代碼放在檔案jquery.extend.support.js中。jQuery各子產品通過浏覽器功能測試子產品的測試結果來解決浏覽器不相容問題。

function jQuery_support(jQuery){

    jQuery.support = (function() {

        var support,

            all,

            a,

            select,

            opt,

            input,

            marginDiv,

            fragment,

            tds,

            events,

            eventName,

            i,

            isSupported,

            div = document.createElement( "div" ),

            documentElement = document.documentElement;

        // Preliminary tests

        div.setAttribute("className", "t");

        div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";

        all = div.getElementsByTagName( "*" );

        a = div.getElementsByTagName( "a" )[ 0 ];

        // Can't get basic test support

        if ( !all || !all.length || !a ) {

            return {};

        // First batch of supports tests

        select = document.createElement( "select" );

        opt = select.appendChild( document.createElement("option") );

        input = div.getElementsByTagName( "input" )[ 0 ];

        support = {

            // IE strips leading whitespace when .innerHTML is used

            leadingWhitespace: ( div.firstChild.nodeType === 3 ),

            // Make sure that tbody elements aren't automatically inserted

            // IE will insert them into empty tables

            tbody: !div.getElementsByTagName("tbody").length,

            // Make sure that link elements get serialized correctly by innerHTML

            // This requires a wrapper element in IE

            htmlSerialize: !!div.getElementsByTagName("link").length,

            // Get the style information from getAttribute

            // (IE uses .cssText instead)

            style: /top/.test( a.getAttribute("style") ),

            // Make sure that URLs aren't manipulated

            // (IE normalizes it by default)

            hrefNormalized: ( a.getAttribute("href") === "/a" ),

            // Make sure that element opacity exists

            // (IE uses filter instead)

            // Use a regex to work around a WebKit issue. See #5145

            opacity: /^0.55/.test( a.style.opacity ),

            // Verify style float existence

            // (IE uses styleFloat instead of cssFloat)

            cssFloat: !!a.style.cssFloat,

            // Make sure that if no value is specified for a checkbox

            // that it defaults to "on".

            // (WebKit defaults to "" instead)

            checkOn: ( input.value === "on" ),

            // Make sure that a selected-by-default option has a working selected property.

            // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)

            optSelected: opt.selected,

            // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)

            getSetAttribute: div.className !== "t",

            // Tests for enctype support on a form(#6743)

            enctype: !!document.createElement("form").enctype,

            // Makes sure cloning an html5 element does not cause problems

            // Where outerHTML is undefined, this still works

            html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",

            // Will be defined later

            submitBubbles: true,

            changeBubbles: true,

            focusinBubbles: false,

            deleteExpando: true,

            noCloneEvent: true,

            inlineBlockNeedsLayout: false,

            shrinkWrapBlocks: false,

            reliableMarginRight: true

        // Make sure checked status is properly cloned

        input.checked = true;

        support.noCloneChecked = input.cloneNode( true ).checked;

        // Make sure that the options inside disabled selects aren't marked as disabled

        // (WebKit marks them as disabled)

        select.disabled = true;

        support.optDisabled = !opt.disabled;

        // Test to see if it's possible to delete an expando from an element

        // Fails in Internet Explorer

            delete div.test;

        } catch( e ) {

            support.deleteExpando = false;

        if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {

            div.attachEvent( " function() {

                // Cloning a node shouldn't copy over any

                // bound event handlers (IE does this)

                support.noCloneEvent = false;

            div.cloneNode( true ).fireEvent( " );

        // Check if a radio maintains its value

        // after being appended to the DOM

        input = document.createElement("input");

        input.value = "t";

        input.setAttribute("type", "radio");

        support.radioValue = input.value === "t";

        input.setAttribute("checked", "checked");

        div.appendChild( input );

        fragment = document.createDocumentFragment();

        fragment.appendChild( div.lastChild );

        // WebKit doesn't clone checked state correctly in fragments

        support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;

        // Check if a disconnected checkbox will retain its checked

        // value of true after appended to the DOM (IE6/7)

        support.appendChecked = input.checked;

        fragment.removeChild( input );

        fragment.appendChild( div );

        div.innerHTML = "";

        // Check if div with explicit width and no margin-right incorrectly

        // gets computed margin-right based on width of container. For more

        // info see bug #3333

        // Fails in WebKit before Feb 2011 nightlies

        // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right

        if ( window.getComputedStyle ) {

            marginDiv = document.createElement( "div" );

            marginDiv.style.width = "0";

            marginDiv.style.marginRight = "0";

            div.style.width = "2px";

            div.appendChild( marginDiv );

            support.reliableMarginRight =

                ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;

        // Technique from Juriy Zaytsev

        // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/

        // We only care about the case where non-standard event systems

        // are used, namely in IE. Short-circuiting here helps us to

        // avoid an eval call (in setAttribute) which can cause CSP

        // to go haywire. See: https://developer.mozilla.org/en/Security/CSP

        if ( div.attachEvent ) {

            for( i in {

                submit: 1,

                change: 1,

                focusin: 1

            }) {

                eventName = "on" + i;

                isSupported = ( eventName in div );

                if ( !isSupported ) {

                    div.setAttribute( eventName, "return;" );

                    isSupported = ( typeof div[ eventName ] === "function" );

                support[ i + "Bubbles" ] = isSupported;

        fragment.removeChild( div );

        // Null elements to avoid leaks in IE

        fragment = select = opt = marginDiv = div = input = null;

        // Run tests that need a body at doc ready

        jQuery(function() {

            var container, outer, inner, table, td, offsetSupport,

                conMarginTop, ptlm, vb, style, html,

                body = document.getElementsByTagName("body")[0];

            if ( !body ) {

                // Return for frameset docs that don't have a body

                return;

            conMarginTop = 1;

            ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";

            vb = "visibility:hidden;border:0;";

            style = "style='" + ptlm + "border:5px solid #000;padding:0;'";

            html = "<div " + style + "><div></div></div>" +

                "<table " + style + " cellpadding='0' cellspacing='0'>" +

                "<tr><td></td></tr></table>";

            container = document.createElement("div");

            container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";

            body.insertBefore( container, body.firstChild );

            // Construct the test element

            div = document.createElement("div");

            container.appendChild( div );

            // Check if table cells still have offsetWidth/Height when they are set

            // to display:none and there are still other visible table cells in a

            // table row; if so, offsetWidth/Height are not reliable for use when

            // determining if an element has been hidden directly using

            // display:none (it is still safe to use offsets if a parent element is

            // hidden; don safety goggles and see bug #4512 for more information).

            // (only IE 8 fails this test)

            div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";

            tds = div.getElementsByTagName( "td" );

            isSupported = ( tds[ 0 ].offsetHeight === 0 );

            tds[ 0 ].style.display = "";

            tds[ 1 ].style.display = "none";

            // Check if empty table cells still have offsetWidth/Height

            // (IE <= 8 fail this test)

            support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );

            // Figure out if the W3C box model works as expected

            div.innerHTML = "";

            div.style.width = div.style.paddingLeft = "1px";

            jQuery.boxModel = support.boxModel = div.offsetWidth === 2;

            if ( typeof div.style.zoom !== "undefined" ) {

                // Check if natively block-level elements act like inline-block

                // elements when setting their display to 'inline' and giving

                // them layout

                // (IE < 8 does this)

                div.style.display = "inline";

                div.style.zoom = 1;

                support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );

                // Check if elements with layout shrink-wrap their children

                // (IE 6 does this)

                div.style.display = "";

                div.innerHTML = "<div style='width:4px;'></div>";

                support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );

            div.style.cssText = ptlm + vb;

            div.innerHTML = html;

            outer = div.firstChild;

            inner = outer.firstChild;

            td = outer.nextSibling.firstChild.firstChild;

            offsetSupport = {

                doesNotAddBorder: ( inner.offsetTop !== 5 ),

                doesAddBorderForTableAndCells: ( td.offsetTop === 5 )

            };

            inner.style.position = "fixed";

            inner.style.top = "20px";

            // safari subtracts parent border width here which is 5px

            offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );

            inner.style.position = inner.style.top = "";

            outer.style.overflow = "hidden";

            outer.style.position = "relative";

            offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );

            offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );

            body.removeChild( container );

            div  = container = null;

            jQuery.extend( support, offsetSupport );

        });

        return support;

8、屬性操作

我們将jQuery有關屬性操作的相關方法代碼放在檔案jquery.extend.attrs.js中。

function jQuery_extend_attributes(jQuery){

        attr: function( name, value ) {

            return jQuery.access( this, name, value, true, jQuery.attr );

        removeAttr: function( name ) {

            return this.each(function() {

                jQuery.removeAttr( this, name );

        prop: function( name, value ) {

            return jQuery.access( this, name, value, true, jQuery.prop );

        removeProp: function( name ) {

            name = jQuery.propFix[ name ] || name;

                // try/catch handles cases where IE balks (such as removing a property on window)

                    this[ name ] = undefined;

                    delete this[ name ];

                } catch( e ) {}

        addClass: function( value ) {

            var classNames, i, l, elem,

                setClass, c, cl;

            if ( jQuery.isFunction( value ) ) {

                return this.each(function( j ) {

                    jQuery( this ).addClass( value.call(this, j, this.className) );

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

                classNames = value.split( rspace );

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

                    elem = this[ i ];

                    if ( elem.nodeType === 1 ) {

                        if ( !elem.className && classNames.length === 1 ) {

                            elem.className = value;

                            setClass = " " + elem.className + " ";

                            for ( c = 0, cl = classNames.length; c < cl; c++ ) {

                                if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {

                                    setClass += classNames[ c ] + " ";

                            elem.className = jQuery.trim( setClass );

            return this;

        removeClass: function( value ) {

            var classNames, i, l, elem, className, c, cl;

                    jQuery( this ).removeClass( value.call(this, j, this.className) );

            if ( (value && typeof value === "string") || value === undefined ) {

                classNames = ( value || "" ).split( rspace );

                    if ( elem.nodeType === 1 && elem.className ) {

                        if ( value ) {

                            className = (" " + elem.className + " ").replace( rclass, " " );

                                className = className.replace(" " + classNames[ c ] + " ", " ");

                            elem.className = jQuery.trim( className );

                            elem.className = "";

        toggleClass: function( value, stateVal ) {

            var type = typeof value,

                isBool = typeof stateVal === "boolean";

                return this.each(function( i ) {

                    jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );

                if ( type === "string" ) {

                    // toggle individual class names

                    var className,

                        i = 0,

                        self = jQuery( this ),

                        state = stateVal,

                        classNames = value.split( rspace );

                    while ( (className = classNames[ i++ ]) ) {

                        // check each className given, space seperated list

                        state = isBool ? state : !self.hasClass( className );

                        self[ state ? "addClass" : "removeClass" ]( className );

                } else if ( type === "undefined" || type === "boolean" ) {

                    if ( this.className ) {

                        // store className if set

                        jQuery._data( this, "__className__", this.className );

                    // toggle whole className

                    this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";

        hasClass: function( selector ) {

            var className = " " + selector + " ",

                l = this.length;

            for ( ; i < l; i++ ) {

                if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {

        val: function( value ) {

            var hooks, ret, isFunction,

                elem = this[0];

            if ( !arguments.length ) {

                    hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];

                    if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {

                        return ret;

                    ret = elem.value;

                    return typeof ret === "string" ?

                        // handle most common string cases

                        ret.replace(rreturn, "") :

                        // handle cases where value is null/undef or number

                        ret == null ? "" : ret;

            isFunction = jQuery.isFunction( value );

            return this.each(function( i ) {

                var self = jQuery(this), val;

                if ( this.nodeType !== 1 ) {

                    return;

                if ( isFunction ) {

                    val = value.call( this, i, self.val() );

                    val = value;

                // Treat null/undefined as ""; convert numbers to string

                if ( val == null ) {

                    val = "";

                } else if ( typeof val === "number" ) {

                    val += "";

                } else if ( jQuery.isArray( val ) ) {

                    val = jQuery.map(val, function ( value ) {

                        return value == null ? "" : value + "";

                    });

                hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];

                // If set returns undefined, fall back to normal setting

                if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {

                    this.value = val;

(待續)