jquery Selectorソース分析


/**
 * author:prk
 * date:2008-08-04
 * comment:comment for selector of jQuery
 * 
 */
var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417
		? "(?:[\\w*_-]|\\\\.)"
		: "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)", quickChild = new RegExp("^>\\s*("
		+ chars + "+)"), quickID = new RegExp("^(" + chars + "+)(#)(" + chars
		+ "+)"), // ^((?:[\\w*_-]|\\\\.))(#)((?:[\\w*_-]|\\\\.))
quickClass = new RegExp("^([#.]?)(" + chars + "*)");// ^([#.]?)((?:[\\w*_-]|\\\\.)*)

jQuery.extend( {
	expr : {
		"" : function(a, i, m) {
			return m[2] == "*" || jQuery.nodeName(a, m[2]);
		},
		"#" : function(a, i, m) {
			return a.getAttribute("id") == m[2];
		},
		":" : {
			// Position Checks
		lt : function(a, i, m) {
			return i < m[3] - 0;
		},
		gt : function(a, i, m) {
			return i > m[3] - 0;
		},
		nth : function(a, i, m) {
			return m[3] - 0 == i;
		},
		eq : function(a, i, m) {
			return m[3] - 0 == i;
		},
		first : function(a, i) {
			return i == 0;
		},
		last : function(a, i, m, r) {
			return i == r.length - 1;
		},
		even : function(a, i) {
			return i % 2 == 0;
		},
		odd : function(a, i) {
			return i % 2;
		},

		// Child Checks
		"first-child" : function(a) {
			return a.parentNode.getElementsByTagName("*")[0] == a;
		},
		"last-child" : function(a) {
			return jQuery.nth(a.parentNode.lastChild, 1, "previousSibling") == a;
		},
		"only-child" : function(a) {
			return !jQuery.nth(a.parentNode.lastChild, 2, "previousSibling");
		},

		// Parent Checks
		parent : function(a) {
			return a.firstChild;
		},
		empty : function(a) {
			return !a.firstChild;
		},

		// Text Check
		contains : function(a, i, m) {
			return (a.textContent || a.innerText || jQuery(a).text() || "")
					.indexOf(m[3]) >= 0;
		},

		// Visibility
		visible : function(a) {
			return "hidden" != a.type && jQuery.css(a, "display") != "none"
					&& jQuery.css(a, "visibility") != "hidden";
		},
		hidden : function(a) {
			return "hidden" == a.type || jQuery.css(a, "display") == "none"
					|| jQuery.css(a, "visibility") == "hidden";
		},

		// Form attributes
		enabled : function(a) {
			return !a.disabled;
		},
		disabled : function(a) {
			return a.disabled;
		},
		checked : function(a) {
			return a.checked;
		},
		selected : function(a) {
			return a.selected || jQuery.attr(a, "selected");
		},

		// Form elements
		text : function(a) {
			return "text" == a.type;
		},
		radio : function(a) {
			return "radio" == a.type;
		},
		checkbox : function(a) {
			return "checkbox" == a.type;
		},
		file : function(a) {
			return "file" == a.type;
		},
		password : function(a) {
			return "password" == a.type;
		},
		submit : function(a) {
			return "submit" == a.type;
		},
		image : function(a) {
			return "image" == a.type;
		},
		reset : function(a) {
			return "reset" == a.type;
		},
		button : function(a) {
			return "button" == a.type || jQuery.nodeName(a, "button");
		},
		input : function(a) {
			return /input|select|textarea|button/i.test(a.nodeName);
		},

		// :has()
		has : function(a, i, m) {
			return jQuery.find(m[3], a).length;
		},

		// :header
		header : function(a) {
			return /h\d/i.test(a.nodeName);
		},

		// :animated
		animated : function(a) {
			return jQuery.grep(jQuery.timers, function(fn) {
				return a == fn.elem;
			}).length;
		}
	}
},

// The regular expressions that power the parsing engine
parse : [
// Match: [@value='test'], [@foo]
		/^(\[) *@?([\w:-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,

		// Match: :contains('foo')
		/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,

		// Match: :even, :last-child, #id, .class
		new RegExp("^([:.#]*)(" + chars + "+)")],

multiFilter : function(expr, elems, not) {
	var old, cur = [];

	while (expr && expr != old) {//      
		old = expr;
		var f = jQuery.filter(expr, elems, not);
		expr = f.t.replace(/^\s*,\s*/, "");
		cur = not ? elems = f.r : jQuery.merge(cur, f.r);
	}

	return cur;
},

find : function(t, context) {
	if (typeof t != "string")
		return [t];//           

	if (context && context.nodeType != 1 && context.nodeType != 9)
		return [];//   context DOM   document

	context = context || document;//    context

	//    ,ret:result, done:    ,last:    t,nodeName:   , p
	var ret = [context], done = [], last, nodeName;

	while (t && last != t) {// t  ,   
		var r = []; // ret tempValue
		last = t; // last:    t
		t = jQuery.trim(t);//      

		var foundToken = false, re = quickChild, //  >   regexp
		m = re.exec(t);

		if (m) {//         >  

			nodeName = m[1].toUpperCase();

			//           regexp nodeName      。
			for (var i = 0;ret[i]; i++)
				for (var c = ret[i].firstChild;c; c = c.nextSibling)
					if (c.nodeType == 1
							&& (nodeName == "*" || c.nodeName.toUpperCase() == nodeName))
						r.push(c);

			ret = r; //               (context)
			t = t.replace(re, "");// remove        
			if (t.indexOf(" ") == 0)//     "E F"     
				continue;//         

			foundToken = true;//     

		} else {//         +~  

			re = /^([>+~])\s*(\w*)/i;

			if ((m = re.exec(t)) != null) {//  +~   
				r = [];
				var merge = {};
				nodeName = m[2].toUpperCase();//    
				m = m[1];//   , +,~
				//     t  " " >(   ), context           ,
				//     t  ~ +(    ),  context          
				for (var j = 0, rl = ret.length;j < rl; j++) {//        (context)  
					//  ~ +            
					var n = (m == "~" || m == "+"
							? ret[j].nextSibling
							: ret[j].firstChild);
					for (;n; n = n.nextSibling)
						if (n.nodeType == 1) {//          
							var id = jQuery.data(n);//  n         id

							if (m == "~" && merge[id])//   ret      
								break;// nextSibling         ?

							if (!nodeName
									|| n.nodeName.toUpperCase() == nodeName) {
								if (m == "~")//         
									merge[id] = true;
								r.push(n);
							}

							if (m == "+")//         ,       。
								break;
						}
				}

				ret = r;//          
				t = jQuery.trim(t.replace(re, ""));
				foundToken = true;
			}
		}

		if (t && !foundToken) {//    >~+   

			if (!t.indexOf(",")) {// ,           

				if (context == ret[0])
					ret.shift();//        context   ret
				done = jQuery.merge(done, ret);// ret       done

				r = ret = [context];//      

				// Touch up the selector string
				t = " " + t.substr(1, t.length);

			} else {//   ,       
				/*
				 * qId:^((?:[\w*_-]|\.)+)(#)((?:[\w*_-]|\.)+)
				 * qclass:^([#.]?)((?:[\\w*_-]|\.)*)
				 */
				var re2 = quickID;//  (.)nodeName#idName
				var m = re2.exec(t);//         

				if (m) {
					m = [0, m[2], m[3], m[1]];// m=[0,#,idName,nodeName]
				} else {
					re2 = quickClass;// #nodeName,.className
					m = re2.exec(t);// m=[all,#,idName]
				}

				m[2] = m[2].replace(/\\/g, "");//       
				var elem = ret[ret.length - 1];//         

				//   Id              
				if (m[1] == "#" && elem && elem.getElementById
						&& !jQuery.isXMLDoc(elem)) {
					var oid = elem.getElementById(m[2]);

					//      ID    , IE   name    ,   form    
					//     form    name   id   。
					if ((jQuery.browser.msie || jQuery.browser.opera) && oid
							&& typeof oid.id == "string" && oid.id != m[2])
						oid = jQuery('[@id="' + m[2] + '"]', elem)[0];

					//      node Name    , div#foo,      
					ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3]))
							? [oid]
							: [];
				} else {//                       ,    
					for (var i = 0;ret[i]; i++) {
						var tag = (m[1] == "#" && m[3] ? m[3] : (m[1] != ""
								|| m[0] == "" ? "*" : m[2]));//     tagName

						if (tag == "*"
								&& ret[i].nodeName.toLowerCase() == "object")
							tag = "param";// Handle IE7 being really dumb
						// about <object>s

						r = jQuery.merge(r, ret[i].getElementsByTagName(tag));
					}

					if (m[1] == ".") //   class          
						r = jQuery.classFilter(r, m[2]);

					if (m[1] == "#") {//     id  ,  regexp  id   
						var tmp = [];
						for (var i = 0;r[i]; i++)
							if (r[i].getAttribute("id") == m[2]) {
								tmp = [r[i]];
								break;
							}

						r = tmp;
					}

					ret = r;
				}

				t = t.replace(re2, "");
			}

		}

		if (t) {//      selector,    r          
			var val = jQuery.filter(t, r);
			ret = r = val.r;
			t = jQuery.trim(val.t);//      
		}
	}

	if (t)// selector  ,  []。
		ret = [];

	if (ret && context == ret[0])
		ret.shift();//       

	done = jQuery.merge(done, ret);//   

	return done;
},
//   r element  className   m           
classFilter : function(r, m, not) {
	m = " " + m + " ";
	var tmp = [];
	for (var i = 0;r[i]; i++) {
		var pass = (" " + r[i].className + " ").indexOf(m) >= 0;
		if (!not && pass || not && !pass)
			tmp.push(r[i]);
	}
	return tmp;
},

filter : function(t, r, not) {
	var last;

	while (t && t != last) {// t  ,   
		last = t;

		// Match: [@value='test'], [@foo]
		// 1、^(\[) *@?([\w:-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,

		// Match: :contains('foo')
		// 2、^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,

		// Match: :even, :last-child, #id, .class
		// 3、new RegExp("^([:.#]*)(" + chars + "+)")],

		// isSimple = /^.[^:#\[\.]*$/
		var p = jQuery.parse, m;

		for (var i = 0;p[i]; i++) {//    jQuery.parse regexp   
			m = p[i].exec(t);
			if (m) {
				t = t.substring(m[0].length);
				m[2] = m[2].replace(/\\/g, "");//           \  
				break;
			}
		}

		if (!m)//       regexp    
			break;

		// :not(.class)      .class      
		if (m[1] == ":" && m[2] == "not")
			//       m[3] .class    
			r = isSimple.test(m[3])
					? jQuery.filter(m[3], r, true).r
					: jQuery(r).not(m[3]);
		else if (m[1] == ".")//        
			r = jQuery.classFilter(r, m[2], not);
		else if (m[1] == "[") {// [@value='test']       
			var tmp = [], type = m[3];//   , =

			for (var i = 0, rl = r.length;i < rl; i++) {
				var a = r[i], z = a[jQuery.props[m[2]] || m[2]];//       

				if (z == null || /style|href|src|selected/.test(m[2]))
					z = jQuery.attr(a, m[2]) || '';//        

				// [foo],[foo=aa][foo!=aa][foo^=aa][foo$=aa][foo~=aa]
				if ((type == "" && !!z || type == "=" && z == m[5]
						|| type == "!=" && z != m[5] || type == "^=" && z
						&& !z.indexOf(m[5]) || type == "$="
						&& z.substr(z.length - m[5].length) == m[5] || (type == "*=" || type == "~=")
						&& z.indexOf(m[5]) >= 0)
						^ not)
					tmp.push(a);
			}
			r = tmp;

		} else if (m[1] == ":" && m[2] == "nth-child") {//     
			var merge = {}, tmp = [],
			// parse equations like 'even', 'odd', '5', '2n', '3n+2',
			// '4n-1', '-n+6'
			test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3] == "even" && "2n"
					|| m[3] == "odd" && "2n+1" || !/\D/.test(m[3]) && "0n+"
					+ m[3] || m[3]),

			//    (first)n+(last),first=(-?)(\d*),last=((?:\+|-)?\d*)
			first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;

			for (var i = 0, rl = r.length;i < rl; i++) {
				var node = r[i], parentNode = node.parentNode, id = jQuery
						.data(parentNode);

				if (!merge[id]) {//               ,       
					var c = 1;
					for (var n = parentNode.firstChild;n; n = n.nextSibling)
						if (n.nodeType == 1)
							n.nodeIndex = c++;
					merge[id] = true;
				}

				var add = false;

				if (first == 0) {// 0     
					if (node.nodeIndex == last)
						add = true;
				}
				//   3n+2            0
				else if ((node.nodeIndex - last) % first == 0
						&& (node.nodeIndex - last) / first >= 0)
					add = true;

				if (add ^ not)
					tmp.push(node);
			}

			r = tmp;

		} else {//   m[1]m[2] Query.expr         
			var fn = jQuery.expr[m[1]];
			if (typeof fn == "object")
				fn = fn[m[2]];

			if (typeof fn == "string")
				fn = eval("false||function(a,i){return " + fn + ";}");

			//         r
			r = jQuery.grep(r, function(elem, i) {
				return fn(elem, i, m, r);
			}, not);
		}
	}

	// Return an array of filtered elements (r)
	// and the modified expression string (t)
	return {
		r : r,
		t : t
	};
},

// dir:nextSibling elem:element
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;
},
// dir:nextSibling result:deep cur:current. elem :no use
nth : function(cur, result, dir, elem) {
	result = result || 1;
	var num = 0;

	for (;cur; cur = cur[dir])
		if (cur.nodeType == 1 && ++num == result)
			break;

	return cur;
},

//   elem n         。
sibling : function(n, elem) {
	var r = [];

	for (;n; n = n.nextSibling) {
		if (n.nodeType == 1 && n != elem)
			r.push(n);
	}

	return r;
}
});