jQuery选择器探究:ID选择器

时间:2022-12-18 16:37:53
ID选择器算是jQuery选择器中最简单的一种了,这里跟踪并梳理jQuery id选择器的逻辑。


所有选择器的调用语法都是从jQuery构造函数开始的,id选择器的调用语法是:$("#x"),如果我们是jQuery的设计者,那么在jQuery构造函数中对id选择器的处理有两种思路:要么单独处理,要么与其他选择器一样处理,如果区别对待id选择器,只需要识别选择器/^#(\w)+$/即可,如果不区分于其他选择器,那么统一交给Sizzle处理,jQuery的设计者选择了第一种方案。


由于jQuery构造函数jQuery.fn.init是及其灵活的,可以接受几乎任意类型的参数组合,这里只单独分析selector参数为string类型时的逻辑:
			// Handle HTML strings
			if ( typeof selector === "string" ) {
				// Are we dealing with HTML string or an ID?
				if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
					// Assume that strings that start and end with <> are HTML and skip the regex check
					match = [ null, selector, null ];

				} else {
					match = quickExpr.exec( selector );
				}

				// Verify a match, and that no context was specified for #id
				if ( match && (match[1] || !context) ) {

					// HANDLE: $(html) -> $(array)
					if ( match[1] ) {
						context = context instanceof jQuery ? context[0] : context;
						doc = (context ? context.ownerDocument || context : document);

						// If a single string is passed in and it's a single tag
						// just do a createElement and skip the rest
						ret = rsingleTag.exec( selector );

						if ( ret ) {
							if ( jQuery.isPlainObject( context ) ) {
								selector = [ document.createElement( ret[1] ) ];
								jQuery.fn.attr.call( selector, context, true );

							} else {
								selector = [ doc.createElement( ret[1] ) ];
							}

						} else {
							ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
							selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
						}

						return jQuery.merge( this, selector );

					// HANDLE: $("#id")
					} else {
						elem = document.getElementById( match[2] );

						// 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[2] ) {
								return rootjQuery.find( selector );
							}

							// Otherwise, we inject the element directly into the jQuery object
							this.length = 1;
							this[0] = elem;
						}

						this.context = document;
						this.selector = selector;
						return this;
					}

				// HANDLE: $(expr, $(...))
				} else if ( !context || context.jquery ) {
					return (context || rootjQuery).find( selector );

				// HANDLE: $(expr, context)
				// (which is just equivalent to: $(context).find(expr)
				} else {
					return this.constructor( context ).find( selector );
				}

			}

可以看到selector参数为string时,先判断是否为html标签,$("<HTML TAG/?>(.*</HTML TAG>)?"),这种字符串被jQuery单独处理,我们将在其他篇中单独分析。


接下来根据一个关键的正则表达式来判断接受的selector是否为ID选择器--quickExpr
	// A simple way to check for HTML strings or ID strings
	// (both of which we optimize for)
	quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

quickExpr正则对象用于区分html tag和id selector,其有两个捕获分组,第一个分组捕获html tag内容,第二个分组捕获id标识符,其依据正是特殊字符"#":#([\w\-]*)。match[1]存在时说明捕获的是html tag内容,match[2]存在时说明捕获的是id标识符。这里特别需要注意的是这行总的判断代码:if ( match && (match[1] || !context) ),这里并没有使用if ( match && (match[1] || match[2]) ),说明在使用id selector时一定不能传入第二个参数:$("#id", obj),否则将到传入的第二个参数对象contenx中去查询selector。这样设计是有道理的:一般情况下#id直接在确定的document对象中查找,不需要到特定的jQuery对象或原生dom对象中查找。


判读出selector是id selector后,重点是以下代码:
					// HANDLE: $("#id")
					} else {
						elem = document.getElementById( match[2] );

						// 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[2] ) {
								return rootjQuery.find( selector );
							}

							// Otherwise, we inject the element directly into the jQuery object
							this.length = 1;
							this[0] = elem;
						}

						this.context = document;
						this.selector = selector;
						return this;
					}

对于id选择符,背后的逻辑还是回到基本api上了:"elem = document.getElementById( match[2] );"需要注意的点是,jQuery构造函数最终返回的对象肯定jQuery对象而不是原生的dom对象,因此查找到的elem元素将作为返回的jQuery对象的第一个也是唯一一个元素,同时必须设置jQuery对象的context属性和selector属性,至于特殊的"捕获id分组值与找到的原生对象的id属性值不一致的"情况,是由于部分浏览器不遵守标准规范导致的(document.getElementById的实现为by name而不是by id),将转入"return rootjQuery.find( selector );"逻辑,也就是除id selector外的通用Sizzle定位查找逻辑中,我们将在接下来分析。