<p>在正式深入jQuery的核心功能选择器之前,还有一些方法,基本都是数组方法,用于遴选更具体的需求,如获得某个元素的所有祖选元素啦,等等。接着是其缓存机制data。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //@author 司徒正美|なさみ|cheng http://www.cnblogs.com/rubylouvre/ All rights reserved //去除两边的空白 trim: function( text ) { return (text || "").replace( /^\s+|\s+$/g, "" ); }, //转换成数组,很大众的方法 makeArray: function( array ) { var ret = []; if( array != null ){ var i = array.length; // The window, strings (and functions) also have 'length' if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval ) ret[0] = array;//就只有一元素 else while( i )//处理数组 ret[--i] = array[i]; } return ret; }, //判断是否在数组中,类似indexOf inArray: function( elem, array ) { for ( var i = 0, length = array.length; i < length; i++ ) // Use === because on IE, window == document if ( array[ i ] === elem ) return i; return -1; }, //把新元素或第二个数组加入第一个数组中 //类似数组的concat merge: function( first, second ) { // We have to loop this way because IE & Opera overwrite the length // expando of getElementsByTagName var i = 0, elem, pos = first.length; // Also, we need to make sure that the correct elements are being returned // (IE returns comment nodes in a '*' query) if ( !jQuery.support.getAll ) { while ( (elem = second[ i++ ]) != null ) if ( elem.nodeType != 8 ) first[ pos++ ] = elem; } else while ( (elem = second[ i++ ]) != null ) first[ pos++ ] = elem; return first; }, //过滤重复元素,用done这个普通对象做过滤器(因为键如果同名将被覆盖掉) unique: function( array ) { var ret = [], done = {}; try { for ( var i = 0, length = array.length; i < length; i++ ) { var id = jQuery.data( array[ i ] ); if ( !done[ id ] ) { done[ id ] = true; ret.push( array[ i ] ); } } } catch( e ) { ret = array; } return ret; }, //类似数组的filter,这方法起得真不好,通常这都是与正则有关的…… //$.grep( [0,1,2], function(n,i){ // return n > 0; //}); //[1, 2] grep: function( elems, callback, inv ) { var ret = []; // Go through the array, only saving the items // that pass the validator function //写法很特别,callback之前的!是为了防止回调函数没有返回值 //javascript默认没有返回值的函数都返回undefined,这样一搞 //就变成true,原来返回true的变成false,我们需要负负得正,中和一下 //于是!=出场了,而inv也是未必存在的,用!强制转换成布尔 for ( var i = 0, length = elems.length; i < length; i++ ) if ( !inv != !callback( elems[ i ], i ) ) ret.push( elems[ i ] ); return ret; }, //就是数组中的map map: function( elems, callback ) { var ret = []; // Go through the array, translating each of the items to their // new value (or values). for ( var i = 0, length = elems.length; i < length; i++ ) { var value = callback( elems[ i ], i ); if ( value != null ) ret[ ret.length ] = value; } return ret.concat.apply( [], ret ); } }); // jQuery.browser下面的方法已经被废弃了,这些都是为兼容以前的版本与插件用 var userAgent = navigator.userAgent.toLowerCase(); // Figure out what browser is being used jQuery.browser = { version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1], safari: /webkit/.test( userAgent ), opera: /opera/.test( userAgent ), msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ), mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ) }; //把以下方法parent,parents,next……添加到jQuery的原型上去,都是一些过滤方法 jQuery.each({ parent: function(elem){return elem.parentNode;}, parents: function(elem){return jQuery.dir(elem,"parentNode");}, next: function(elem){return jQuery.nth(elem,2,"nextSibling");}, prev: function(elem){return jQuery.nth(elem,2,"previousSibling");}, nextAll: function(elem){return jQuery.dir(elem,"nextSibling");}, prevAll: function(elem){return jQuery.dir(elem,"previousSibling");}, siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);}, children: function(elem){return jQuery.sibling(elem.firstChild);}, contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);} }, function(name, fn){ jQuery.fn[ name ] = function( selector ) {//方法体 var ret = jQuery.map( this, fn ); if ( selector && typeof selector == "string" ) ret = jQuery.multiFilter( selector, ret ); return this.pushStack( jQuery.unique( ret ), name, selector ); }; }); //把以下方法appendTo,prependTo,insertBefore……添加到jQuery的原型上去, //利用已有的append,prepend……方法构建 jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function(name, original){ jQuery.fn[ name ] = function( selector ) { var ret = [], insert = jQuery( selector ); for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = (i > 0 ? this.clone(true) : this).get(); jQuery.fn[ original ].apply( jQuery(insert[i]), elems ); ret = ret.concat( elems ); } return this.pushStack( ret, name, selector ); }; }); //一些重要常用的静态方法 jQuery.each({ removeAttr: function( name ) { jQuery.attr( this, name, "" ); if (this.nodeType == 1) this.removeAttribute( name ); }, addClass: function( classNames ) { jQuery.className.add( this, classNames ); }, removeClass: function( classNames ) { jQuery.className.remove( this, classNames ); }, toggleClass: function( classNames, state ) { if( typeof state !== "boolean" ) state = !jQuery.className.has( this, classNames ); jQuery.className[ state ? "add" : "remove" ]( this, classNames ); }, remove: function( selector ) { if ( !selector || jQuery.filter( selector, [ this ] ).length ) { // Prevent memory leaks jQuery( "*", this ).add([this]).each(function(){ jQuery.event.remove(this);//★★★★★ jQuery.removeData(this); }); if (this.parentNode) this.parentNode.removeChild( this ); } }, empty: function() { // Remove element nodes and prevent memory leaks jQuery(this).children().remove(); // Remove any remaining nodes while ( this.firstChild ) this.removeChild( this.firstChild ); } }, function(name, fn){ jQuery.fn[ name ] = function(){ return this.each( fn, arguments ); }; }); //将带单位的数值去掉单位 // Helper function used by the dimensions and offset modules function num(elem, prop) { return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0; } </pre> <p>接着下来看jQuery的缓存机制,jQuery的性能很大部分依仗于它。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //@author 司徒正美|RestlessDream|なさみ|cheng http://www.cnblogs.com/rubylouvre/ All rights reserved var expando = "jQuery" + now(), uuid = 0, windowData = {}; jQuery.extend({ cache: {}, data: function( elem, name, data ) { //坚决不染指window elem = elem == window ? windowData : elem; //在elem上设置一个变量 var id = elem[ expando ]; // Compute a unique ID for the element if ( !id ) // 同时为id,elem[expando]赋值,值为单一数字 id = elem[ expando ] = ++uuid; // Only generate the data cache if we're // trying to access or manipulate it if ( name && !jQuery.cache[ id ] ) //在jQuery.cache上开辟一个对象,专门用于储存与那个elem有关的东西 jQuery.cache[ id ] = {}; // Prevent overriding the named cache with undefined values if ( data !== undefined )//data必须定义 jQuery.cache[ id ][ name ] = data; // Return the named cache data, or the ID for the element //根据第二个参数是否存在决定返回的是缓存数据还是element的特别ID return name ? jQuery.cache[ id ][ name ] : id; }, //移除缓存数据 removeData: function( elem, name ) { elem = elem == window ? windowData : elem; var id = elem[ expando ]; // If we want to remove a specific section of the element's data if ( name ) { if ( jQuery.cache[ id ] ) { // Remove the section of cache data delete jQuery.cache[ id ][ name ]; // If we've removed all the data, remove the element's cache name = ""; for ( name in jQuery.cache[ id ] ) break; if ( !name ) jQuery.removeData( elem ); } // Otherwise, we want to remove all of the element's data } else { // Clean up the element expando try { //IE不能直接用delete去移除,要用removeAttribute delete elem[ expando ]; } catch(e){ // IE has trouble directly removing the expando // but it's ok with using removeAttribute if ( elem.removeAttribute ) elem.removeAttribute( expando ); } // Completely remove the data cache //用缓存体中把其索引值也移掉 delete jQuery.cache[ id ]; } }, //缓存元素的类组数属性 //可读写 queue: function( elem, type, data ) { if ( elem ){ type = (type || "fx") + "queue"; var q = jQuery.data( elem, type ); if ( !q || jQuery.isArray(data) ) //q是数组 q = jQuery.data( elem, type, jQuery.makeArray(data) ); else if( data ) q.push( data ); } return q; }, //对元素的类数组缓存进行dequeue(也就是shift) dequeue: function( elem, type ){ var queue = jQuery.queue( elem, type ), fn = queue.shift(); if( !type || type === "fx" ) fn = queue[0]; if( fn !== undefined ) fn.call(elem); } }); //让jQuery对象也能获得这种缓存能力 //都是用上面静态方法实现,最终的缓存体还是jQuery.cache jQuery.fn.extend({ data: function( key, value ){ var parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); if ( data === undefined && this.length ) data = jQuery.data( this[0], key ); return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ jQuery.data( this, key, value ); }); }, removeData: function( key ){ return this.each(function(){ jQuery.removeData( this, key ); }); }, queue: function(type, data){ if ( typeof type !== "string" ) { data = type; type = "fx"; } if ( data === undefined ) return jQuery.queue( this[0], type ); return this.each(function(){ var queue = jQuery.queue( this, type, data ); if( type == "fx" && queue.length == 1 ) queue[0].call(this); }); }, dequeue: function(type){ return this.each(function(){ jQuery.dequeue( this, type ); }); } }); 六 <p>今天我开始攻略jQuery的心脏,css选择器。不过Sizzle是如此复杂的东西,我发现不能跟着John Resig的思路一行行读下去,因此下面的代码和jQuery的次序是不一样的。</p> <p>jQuery的代码是包含在一个巨大的闭包中,Sizzle又在它里面开辟另一个闭包。它是完全独立于jQuery,jQuery通过find方法来调用Sizzle。一开始是这几个变量,尤其是那个正则,用于分解我们传入的字符串</p> <pre class="brush:javascript;gutter:false;toolbar:false"> var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g, done = 0, toString = Object.prototype.toString; </pre> <p>然后我们看其表达式,用于深加工与过滤以及简单的查找:</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //@author 司徒正美|なさみ|cheng http://www.cnblogs.com/rubylouvre/ All rights reserved 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|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ }, attrMap: {//一些属性不能直接其HTML名字去取,需要用其在javascript的属性名 "class": "className", "for": "htmlFor" }, attrHandle: { href: function(elem){ return elem.getAttribute("href"); } }, relative: { //相邻选择符 "+": function(checkSet, part, isXML){ var isPartStr = typeof part === "string", isTag = isPartStr && !/\W/.test(part), isPartStrNotTag = isPartStr && !isTag; if ( isTag && !isXML ) { part = part.toUpperCase(); } 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 === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, //亲子选择符 ">": function(checkSet, part, isXML){ var isPartStr = typeof part === "string"; if ( isPartStr && !/\W/.test(part) ) { part = isXML ? part : part.toUpperCase(); for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName === part ? parent : false; } } } else { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, //后代选择符 "": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck; if ( !part.match(/\W/) ) { var nodeCheck = part = isXML ? part : part.toUpperCase(); checkFn = dirNodeCheck; } checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); }, //兄长选择符 "~": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !part.match(/\W/) ) { var nodeCheck = part = isXML ? part : part.toUpperCase(); checkFn = dirNodeCheck; } 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]); return m ? [m] : [];//就算只有一个也放进数组 } }, NAME: function(match, context, isXML){ 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){ return context.getElementsByTagName(match[1]); } }, preFilter: {//这里,如果符合的话都返回字符串 CLASS: function(match, curLoop, inplace, result, not, isXML){ match = " " + match[1].replace(/\\/g, "") + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { //相当于hasClassName if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { if ( !inplace ) result.push( elem ); } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function(match){ return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ for ( var i = 0; curLoop[i] === false; i++ ){} return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); }, CHILD: function(match){ //把nth(****)里面的表达式都弄成an+b的样子 if ( match[1] == "nth" ) { // 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; } // TODO: Move to normal caching system match[0] = done++; return match; }, ATTR: function(match, curLoop, inplace, result, not, isXML){ var name = match[1].replace(/\\/g, ""); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function(match, curLoop, inplace, result, not){ if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function(match){ match.unshift( true ); return match; } }, 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 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){ //是否是h1,h2,h3,h4,h5,h6 return /h\d/i.test( elem.nodeName ); }, text: function(elem){ //文本域,下面几个相仿,基本上可以归类于属性选择器 return "text" === elem.type; }, radio: function(elem){ return "radio" === elem.type; }, checkbox: function(elem){ return "checkbox" === elem.type; }, file: function(elem){ return "file" === elem.type; }, password: function(elem){ return "password" === elem.type; }, submit: function(elem){ return "submit" === elem.type; }, image: function(elem){ return "image" === elem.type; }, reset: function(elem){ return "reset" === elem.type; }, button: function(elem){ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; }, input: function(elem){ return /input|select|textarea|button/i.test(elem.nodeName); } }, 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){ return match[3] - 0 == i; } }, 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 || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var i = 0, l = not.length; i < l; i++ ) { if ( not[i] === elem ) { return false; } } return true; } }, CHILD: function(elem, match){ var 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) { if ( node.nodeType === 1 ) return false; } return true; case 'nth': var first = match[2], last = match[3]; if ( first == 1 && last == 0 ) { return true; } var doneName = match[0], parent = elem.parentNode; if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count;//添加一个私有属性 } } parent.sizcache = doneName; } var 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 === match; }, CLASS: function(elem, match){ return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function(elem, match){ var name = match[1], result = 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 === "=" ? 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 ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS; </pre> <div><img src="http://images.cnblogs.com/cnblogs_com/rubylouvre/205314/o_Sizzle.gif"></div> <p>但上图没有完全显现Sizzle复杂的工作机制,它是从左到右工作,加工了一个字符串,查找,然后过滤非元素节点,再跟据其属性或内容或在父元素的顺序过滤,然后到下一个字符串,这时搜索起点就是上次的结果数组的元素节点,想象一下草根的样子吧。在许多情况下,选择器都是靠工作的,element.getElementsByTagName(*),获得其一元素的所有子孙,因此Expr中的过滤器特别多。为了过快查找速度,如有些浏览器已经实现了getElementsByClassName,jQuery也设法把它们利用起来。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> for ( var type in Expr.match ) { //重写Expr.match中的正则,利用负向零宽断言让其更加严谨 Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); } </pre> <p>接着下来我们还是未到时候看上面的主程序,继续看它的辅助方法。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //把NodeList HTMLCollection转换成纯数组,如果有第二参数(上次查找的结果),则把它们加入到结果集中 var makeArray = function(array, results) { array = Array.prototype.slice.call( array ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; try { //基本上是用于测试IE的,IE的NodeList HTMLCollection不支持用数组的slice转换为数组 Array.prototype.slice.call( document.documentElement.childNodes ); //这时就要重载makeArray,一个个元素搬入一个空数组中了 } catch(e){ makeArray = function(array, results) { var ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var i = 0, l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( var i = 0; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } 七 <p>在Sizzle中有许多有用的辅助方法,我们继续一个个看。其中涉及许多BUG的修正以及一些很少见的API。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //@author 司徒正美|なさみ|cheng http://www.cnblogs.com/rubylouvre/ All rights reserved var sortOrder;//比较两个元素在页面上的顺序,返回正数,0,负数 //如果支持compareDocumentPosition方法,新锐的标准浏览器都支持 //我在《javascript contains方法》一文中有详细介绍 //http://www.cnblogs.com/rubylouvre/archive/2009/10/14/1583523.html if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { //节点a 在节点b 之前, var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; //用于IE //sourceIndex是指元素在NodeList中的位置 } else if ( "sourceIndex" in document.documentElement ) { sortOrder = function( a, b ) { var ret = a.sourceIndex - b.sourceIndex; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; //用于旧式的标准游览器 } else if ( document.createRange ) { sortOrder = function( a, b ) { var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); aRange.selectNode(a); aRange.collapse(true); bRange.selectNode(b); bRange.collapse(true); //比较两个selection的位置 //https://developer.mozilla.org/en/DOM/range.compareBoundaryPoints var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } </pre> <p>比较元素位置在IE还可以用uniqueNumber,都是自上至下分配数字。</p> <p>下面对getElementById,getElementsByTagName,getElementsByClassName, querySelectorAll 进行调整。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //在getElementById(XXX)在IE中有bug,它会找第一个属性name或id等于XXX的元素, //尤其是在表单元素中,它们通常都带有name属性 (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("form"), id = "script" + (new Date).getTime(); form.innerHTML = "<input name='" + id + "'/>"; // Inject it into the root element, check its status, and remove it quickly var root = document.documentElement; 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 Expr.find.ID = function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); //确定此元素是否显式为id赋值 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function(elem, match){ //确定此元素是否显式为id赋值 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); })(); (function(){ // 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 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 //处理href属性,如果第二个参数,IE返回的是绝对路径 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); }; } })(); if ( document.querySelectorAll ) (function(){ //创建一个元素片段<div><p class='TEST'></p></div> //用querySelectorAll看看能否正确找到这个p元素 var oldSizzle = Sizzle, div = document.createElement("div"); 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; } //如果能,就用querySelectorAll重载整个Sizzle引擎,效率最高!!! 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 && context.nodeType === 9 && !isXML(context) ) { try { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} } return oldSizzle(query, context, extra, seed); }; Sizzle.find = oldSizzle.find; Sizzle.filter = oldSizzle.filter; Sizzle.selectors = oldSizzle.selectors; Sizzle.matches = oldSizzle.matches; })(); if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ //创建一个元素片段<div><div class='test e'></div><div class='test'></div></div> //用getElementsByClassName看看能否正确找到这两个div元素 var div = document.createElement("div"); div.innerHTML = "<div class='test e'></div><div class='test'></div>"; // Opera can't find a second classname (in 9.6) if ( 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 ) return; //重新调整与CLASS有关的逻辑 Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context, isXML) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; })(); </pre> <pre class="brush:javascript;gutter:false;toolbar:false"> //这东西用于后代选择器与兄长选择器,取得某范围中所有元素,并且防止重复取得 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { var sibDir = dir == "previousSibling" && !isXML; //checkSet为元素集合,doneName为数字 for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { if ( sibDir && elem.nodeType === 1 ){ elem.sizcache = doneName;//设置一标记,以后有与它值相等的不重复取 elem.sizset = i; } elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) {//比较是否相等 match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem.sizcache = doneName; elem.sizset = i; } if ( elem.nodeName === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } //和上面功能差不多,不知是否出于兼容以前版本的需要…… function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { if ( sibDir && elem.nodeType === 1 ) { elem.sizcache = doneName; elem.sizset = i; } elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem.sizcache = 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; } } elem = elem[dir]; } checkSet[i] = match; } } } </pre> <pre class="brush:javascript;gutter:false;toolbar:false"> //判断一个元素是否包含另一个元素 //http://www.cnblogs.com/rubylouvre/archive/2009/10/14/1583523.html var contains = document.compareDocumentPosition ? function(a, b){ return a.compareDocumentPosition(b) & 16; } : function(a, b){ return a !== b && (a.contains ? a.contains(b) : true); }; //判断是否为XML var isXML = function(elem){ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || !!elem.ownerDocument && isXML( elem.ownerDocument ); }; //主要是处理结构伪类中的子元素过滤器 var posProcess = function(selector, context){ var tmpSet = [], later = "", match, 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; //回调Sizzle for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet ); } return Sizzle.filter( later, tmpSet ); }; 八 <p>今天把jQuery的Sizzle选择器引擎讲完。最后给出其大体的工作流程。这东西非常复杂,不要妄图看一遍就明白了。无论看懂与否,多看点源码,还是有裨益的。至少在处理循环结构上有收获吧。</p> <pre class="brush:javascript;gutter:false;toolbar:false"> //@author 司徒正美|なさみ|cheng http://www.cnblogs.com/rubylouvre/ All rights reserved // EXPOSE jQuery.find = Sizzle; jQuery.filter = Sizzle.filter; jQuery.expr = Sizzle.selectors; //以:开头许多都是自定义伪类 jQuery.expr[":"] = jQuery.expr.filters; //css属性display引起的元素不可见 Sizzle.selectors.filters.hidden = function(elem){ return elem.offsetWidth === 0 || elem.offsetHeight === 0; }; //css属性display引起的元素不可见 Sizzle.selectors.filters.visible = function(elem){ return elem.offsetWidth > 0 || elem.offsetHeight > 0; }; //是否在运动中 Sizzle.selectors.filters.animated = function(elem){ return jQuery.grep(jQuery.timers, function(fn){ return elem === fn.elem; }).length; }; //重载jQuery.multiFilter jQuery.multiFilter = function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return Sizzle.matches(expr, elems); }; //把路径上的元素放到结果上,dir为parentNode,previousSibling,nextSilbing jQuery.dir = function( elem, dir ){ var matched = [], cur = elem[dir]; while ( cur && cur != document ) { if ( cur.nodeType == 1 ) matched.push( cur ); cur = cur[dir]; } return matched; }; //在内部调用result好像都为2,dir为previousSibling,nextSilbing //用于子元素过滤 jQuery.nth = function(cur, result, dir, elem){ result = result || 1; var num = 0; //如果cur为undefined中止循环 for ( ; cur; cur = cur[dir] ) if ( cur.nodeType == 1 && ++num == result ) break; return cur; }; //查找不等于elem的兄弟元素节点 jQuery.sibling = function(n, elem){ var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType == 1 && n != elem ) r.push( n ); } return r; }; return; window.Sizzle = Sizzle; </pre> <p>好了,回头看Sizzle的主程序部分:</p> <pre class="brush:javascript;gutter:false;toolbar:false"> Sizzle.find = function(expr, context, isXML){ var set, match; if ( !expr ) {//如果不是字符串表达式则返回空数组 return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var type = Expr.order[i], match;//按照ID NAME TAG的优先级顺序执行 //这里可以想象一下 //match = "#aaa".exec( /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/) //然后检测match是否为空数组,空数组相当于false if ( (match = Expr.match[ type ].exec( expr )) ) { //ID的正则 /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/ var left = RegExp.leftContext //如果不是一步到位,是复杂的表达式,需要多次查找与筛选 if ( left.substr( left.length - 1 ) !== "\\" ) { //把换行符去掉,得到正常的字段 //如"#id12\ //34" //去掉后,就得到"#id1234" match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { //移除相应部分的表达, // 如#aaa ee,得到ID对应的元素后,把#aaa去掉, //然后用Expr的表达式来匹配剩下的部分 expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { //返回所有后代 set = context.getElementsByTagName("*"); } return {//返回一个对象 set: set, expr: expr }; }; </pre> <pre class="brush:javascript;gutter:false;toolbar:false"> Sizzle.filter = function(expr, set, inplace, not){ var old = expr, result = [], curLoop = set, match, anyFound, isXMLFilter = set && set[0] && isXML(set[0]); while ( expr && set.length ) { for ( var type in Expr.filter ) { //这是Expr.filter中的键值对 //PSEUDO: function(elem, match, i, array){}, //CHILD: function(elem, match){}, //ID: function(elem, match){}, //TAG: function(elem, match){}, //CLASS: function(elem, match){}, //ATTR: function(elem, match){}, //POS: function(elem, match, i, array){} if ( (match = Expr.match[ type ].exec( expr )) != null ) {//match为数组 var filter = Expr.filter[ type ], found, item;//filter这函数 anyFound = false; if ( curLoop == result ) {//如果结果集为空数组,就让result = []; result = []; } if ( Expr.preFilter[ type ] ) { //这是Expr.preFilter中的键值对 //CLASS: function(match, curLoop, inplace, result, not, isXML){}, //ID: function(match){}, //TAG: function(match, curLoop){}, //CHILD: function(match){ }, //ATTR: function(match, curLoop, inplace, result, not, isXML){}, //PSEUDO: function(match, curLoop, inplace, result, not){ }, //POS: function(match){} //preFilter与filter的功能不同,preFilter对字符串进行调整,好让选择器能找到元素 //filter对查找到的元素或元素数组进行筛选 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) {//如果返回的是false anyFound = found = true;//就把anyFound与found标记为true } else if ( match === true ) { continue; } } if ( match ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { //检测元素是否符合要求 found = filter( item, match, i, curLoop ); var 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;//结果数组将作为一下次要遍历的元素集合返回 } //移除用户输入字符串已查找了的那一部分表达式 expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr == old ) { if ( anyFound == null ) { throw "Syntax error, unrecognized expression: " + expr; } else { break; } } old = expr; } return curLoop; }; </pre> <p>主程序:</p> <pre class="brush:javascript;gutter:false;toolbar:false"> var Sizzle = function(selector, context, results, seed) { results = results || []; context = context || document; if ( context.nodeType !== 1 && context.nodeType !== 9 ) return [];//context必须为DOM元素或document,要不返回空数组 if ( !selector || typeof selector !== "string" ) { return results;//selector必须存在并且为字符串,否则返回上次循环的结果集 } var parts = [], m, set, checkSet, check, mode, extra, prune = true; // Reset the position of the chunker regexp (start from head) chunker.lastIndex = 0; while ( (m = chunker.exec(selector)) !== null ) { parts.push( m[1] ); if ( m[2] ) { extra = RegExp.rightContext;//匹配内容的右边归入extra break; } } //POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, if ( parts.length > 1 && origPOS.exec( selector ) ) { //处理E F E > F E + F E ~ F if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { //这里的parts[0]肯定不是“”,亦即不会是后代选择器 set = posProcess( parts[0] + parts[1], context ); } 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 ); } } } else { var ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) ); set = Sizzle.filter( ret.expr, ret.set ); if ( parts.length > 0 ) { checkSet = makeArray(set); } else { prune = false; } while ( parts.length ) {//倒序的while循环比for循环快 var cur = parts.pop(), pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, isXML(context) ); } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { throw "Syntax error, unrecognized expression: " + (cur || selector); } //数组化NodeList,并加入结果集中 if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context.nodeType === 1 ) { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) {//确保是元素节点 results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, context, results, seed ); if ( sortOrder ) { hasDuplicate = false; results.sort(sortOrder);//重排结果集中的DOM元素,按照原来在网页先后顺序排列 if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) {//确保没有重复的DOM元素,方法比较垃圾 if ( results[i] === results[i-1] ) { results.splice(i--, 1); } } } } } return results; }; </pre> <p>最后重新说一下其逻辑:</p> <ol> <li>首先用一个叫chunker的强大正则,把诸如 var str = " #div , h1#id\<br /> dd.class > span[dd='22222 > 3233'] ul+ li, .class:contain(\"你的+ 999\"),strong span ";这样的字符串,Sizzle称之为selector的东西,分解成一个数组。<img src="http://images.cnblogs.com/cnblogs_com/rubylouvre/202680/o_selectors.gif" /></li> <li>接着对上下文的内容进行判断,确保其为DOM元素或document,否则返回空数组。然后判断selector是否为字符串,由于Sizzle会不断递归调用,selector会越来越短的,直到为零。这些越来越短的selector其实也是第一次chunker 分解的结果之一。不过它们都有可能g再遭分解。每一次循环,这些分解了的字符串都会经过筛选(非空字符),放入parts数组中。</li> <li>这些selector最先会判断一下,是否为亲子兄长相邻后代等关系选择器。由于第一次chunker把大部分空白消灭了,造成了一个不幸的结果,把后代选择器也消灭了。因此必须补上后代选择器。详见后面posProcess的“selector + "*"”操作。</li> <li>在选择器中,也亦即id,tag,name具有查找能力,在标准浏览器中重载了class部分,让getElementsByClassName也能工作。如果querySelectorAll能工作最好不过,整个Sizzle被重载了。总而言之,Sizzle.find所做的工作比较少,它是按[ "ID", "NAME", "TAG" ]的优先级查找元素的。不过在这之前,先要调用Expr.preFilter把连字符"\"造成的字符串破坏进行修复了。如上面的例子,h1#iddd由于中间的连字符串被切成两个部分,成了数组中的两个元素h1#dd与dd。显然这样查找会找不到dd这个ID,后面查找所有dd元素也是错误的,因此必须把它们重新整合成一个元素h1#dddd。</li> <li>根据id,name与tag找到这些元素后,下一个循环就是找它们的子元素或后代元素了,所以Sizzle才会急冲冲地修复后代选择器的问题。至于筛选,Expr有大量的方法来进行。最后是重新排序与去除重复选中的元素,以结果集返回。</li> </ol>