jQuery选择器源码解读(一):Sizzle方法


Posted in Javascript onMarch 31, 2015

对jQuery的Sizzle各方法做了深入分析(同时也参考了一些网上资料)后,将结果分享给大家。我将采用连载的方式,对Sizzle使用的一些方法详细解释一下,每篇文章介绍一个方法。

若需要转载,请写明出处,多谢。

/*
 * Sizzle方法是Sizzle选择器包的主要入口,jQuery的find方法就是调用该方法获取匹配的节点
 * 该方法主要完成下列任务:
 * 1、对于单一选择器,且是ID、Tag、Class三种类型之一,则直接获取并返回结果
 * 2、对于支持querySelectorAll方法的浏览器,通过执行querySelectorAll方法获取并返回匹配的DOM元素
 * 3、除上之外则调用select方法获取并返回匹配的DOM元素
 * 
 * 
 * @param selector 选择器字符串
 * @param context 执行匹配的最初的上下文(即DOM元素集合)。若context没有赋值,则取document。
 * @param results 已匹配出的部分最终结果。若results没有赋值,则赋予空数组。
 * @param seed 初始集合
 */
function Sizzle(selector, context, results, seed) {
	var match, elem, m, nodeType,
	// QSA vars
	i, groups, old, nid, newContext, newSelector;

	/*
	 * preferredDoc = window.document
	 * 
	 * setDocument方法完成一些初始化工作
	 */
	if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
		setDocument(context);
	}

	context = context || document;
	results = results || [];

	/*
	 * 若selector不是有效地字符串类型数据,则直接返回results
	 */
	if (!selector || typeof selector !== "string") {
		return results;
	}

	/*
	 * 若context既不是document(nodeType=9),也不是element(nodeType=1),那么就返回空集合
	 */
	if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) {
		return [];
	}

	// 若当前过滤的是HTML文档,且没有设定seed,则执行if内的语句体
	if (documentIsHTML && !seed) {

		/* 
		 * 若选择器是单一选择器,且是ID、Tag、Class三种类型之一,则直接获取并返回结果
		 * 
		 * rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/
		 * 上述正则表达式括号内三段依次分别用来判断是否是ID、TAG、CLASS类型的单一选择器
		 * 上述正则表达式在最外层圆括号内有三个子表达式(即三个圆括号括起来的部分),
		 *   分别代表ID、Tag、Class选择器的值,在下面代码中,分别体现在match[1]、match[2]、match[3]
		 */
		if ((match = rquickExpr.exec(selector))) {
			// Speed-up: Sizzle("#ID")
			// 处理ID类型选择器,如:#ID
			if ((m = match[1])) {
				// 若当前上下文是一个document,则执行if内语句体
				if (nodeType === 9) {
					elem = context.getElementById(m);
					// 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, Opera, and Webkit
						// return items
						// by name instead of ID
						/*
						 * 一些老版本的浏览器会把name当作ID来处理,
						 * 返回不正确的结果,所以需要再一次对比返回节点的ID属性
						 */ 
						if (elem.id === m) {
							results.push(elem);
							return results;
						}
					} else {
						return results;
					}
				} else {
					// Context is not a document
					/*
					 * contains(context, elem)用来确认获取的elem是否是当前context对象的子对象
					 */
					if (context.ownerDocument
							&& (elem = context.ownerDocument.getElementById(m))
							&& contains(context, elem) && elem.id === m) {
						results.push(elem);
						return results;
					}
				}

				// Speed-up: Sizzle("TAG")
				// 处理Tag类型选择器,如:SPAN
			} else if (match[2]) {
				push.apply(results, context.getElementsByTagName(selector));
				return results;

				// Speed-up: Sizzle(".CLASS")
				/*
				 * 处理class类型选择器,如:.class
				 * 下面条件判断分别是:
				 * m = match[3]:有效的class类型选择器
				 * support.getElementsByClassName 该选择器的div支持getElementsByClassName
				 * context.getElementsByClassName 当前上下文节点有getElementsByClassName方法
				 * 
				 */ 
				
			} else if ((m = match[3]) && support.getElementsByClassName
					&& context.getElementsByClassName) {
				push.apply(results, context.getElementsByClassName(m));
				return results;
			}
		}

		// QSA path
		/*
		 * 若浏览器支持querySelectorAll方法且选择器符合querySelectorAll调用标准,则执行if内语句体
		 * 在这里的检查仅仅是简单匹配
		 * 第一次调用Sizzle时,rbuggyQSA为空
		 * 
		 * if语句体内对当前context对象的id的赋值与恢复,是用来修正querySelectorAll的一个BUG
		 * 该BUG会在某些情况下把当前节点(context)也作为结果返回回来。
		 * 具体方法是,在现有的选择器前加上一个属性选择器:[id=XXX],
		 * XXX 为context的id,若context本身没有设置id,则给个默认值expando。
		 */
		
		if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) {
			nid = old = expando;
			newContext = context;
			// 若context是document,则newSelector取自selector,否则为false
			newSelector = nodeType === 9 && selector;

			// 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
			if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") {
				groups = tokenize(selector);

				if ((old = context.getAttribute("id"))) {
					/*
					 * rescape = /'|\\/g,
					 * 这里将old中的单引号、竖杠、反斜杠前加一个反斜杠
					 * old.replace(rescape, "\\$&")代码中的$&代表匹配项
					 */
					nid = old.replace(rescape, "\\$&");
				} else {
					context.setAttribute("id", nid);
				}
				nid = "[id='" + nid + "'] ";

				// 重新组合新的选择器
				i = groups.length;
				while (i--) {
					groups[i] = nid + toSelector(groups[i]);
				}
				/*
				 * rsibling = new RegExp(whitespace + "*[+~]")
				 * rsibling用于判定选择器是否存在兄弟关系符
				 * 若包含+~符号,则取context的父节点作为当前节点
				 */
				newContext = rsibling.test(selector) && context.parentNode
						|| context;
				newSelector = groups.join(",");
			}

			if (newSelector) {
				/*
				 * 这里之所以需要用try...catch,
				 * 是因为jquery所支持的一些选择器是querySelectorAll所不支持的,
				 * 当使用这些选择器时,querySelectorAll会报非法选择器,
				 * 故需要jquery自身去实现。
				 */
				try {
					// 将querySelectorAll获取的结果并入results,而后返回resulsts
					push.apply(results, newContext
							.querySelectorAll(newSelector));
					return results;
				} catch (qsaError) {
				} finally {
					if (!old) {
						context.removeAttribute("id");
					}
				}
			}
		}
	}

	// All others
	// 除上述快捷方式和调用querySelectorAll方式直接获取结果外,其余都需调用select来获取结果
	/*
	 * rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)"
	 *			+ whitespace + "+$", "g"),
	 * whitespace = "[\\x20\\t\\r\\n\\f]";
	 * 上述rtrim正则表达式的作用是去掉selector两边的空白,空白字符由whitespace变量定义
	 * rtrim的效果与new RegExp("^" + whitespace + "+|" + whitespace + "+$", "g")相似
	 */
	return select(selector.replace(rtrim, "$1"), context, results, seed);
}

各位朋友,若觉得写得不错,帮我顶一下,给点动力,多谢!

Javascript 相关文章推荐
js静态方法与实例方法分析
Jul 04 Javascript
javascript版的in_array函数(判断数组中是否存在特定值)
May 09 Javascript
node.js中的buffer.Buffer.isBuffer方法使用说明
Dec 14 Javascript
jQuery通过deferred对象管理ajax异步
May 20 Javascript
BootStrap中的table实现数据填充与分页应用小结
May 26 Javascript
基于Vue如何封装分页组件
Dec 16 Javascript
vue中计算属性(computed)、methods和watched之间的区别
Jul 27 Javascript
js 判断一个数字是不是2的n次方幂的实例
Nov 26 Javascript
bootstrap下拉框动态赋值方法
Aug 10 Javascript
详解在vue-cli3.0中自定css、js和图片的打包路径
Aug 26 Javascript
小程序分享链接onShareAppMessage的具体用法
May 22 Javascript
webpack 如何同时输出压缩和未压缩的文件的实现步骤
Jun 05 Javascript
JavaScript中创建字典对象(dictionary)实例
Mar 31 #Javascript
jQuery Ajax调用WCF服务详细教程
Mar 31 #Javascript
JavaScript父子窗体间的调用方法
Mar 31 #Javascript
JavaScript操作cookie类实例
Mar 31 #Javascript
javascript实现简单的贪吃蛇游戏
Mar 31 #Javascript
javascript制作2048游戏
Mar 30 #Javascript
JavaScript模拟实现继承的方法
Mar 30 #Javascript
You might like
php中json_encode处理gbk与gb2312中文乱码问题的解决方法
2014/07/10 PHP
PHP的Socket网络编程入门指引
2015/08/11 PHP
分享微信扫码支付开发遇到问题及解决方案-附Ecshop微信支付插件
2015/08/23 PHP
利用js实现前台动态添加文本框,后台获取文本框内容(示例代码)
2013/11/25 Javascript
js 金额格式化来回转换示例
2014/02/23 Javascript
JavaScript实现定时隐藏与显示图片的方法
2015/08/06 Javascript
使用javaScript动态加载Js文件和Css文件
2015/10/24 Javascript
Node.js静态文件服务器改进版
2016/01/10 Javascript
浅谈JQuery+ajax+jsonp 跨域访问
2016/06/25 Javascript
IE8兼容Jquery.validate.js的问题
2016/12/01 Javascript
适用于手机端的jQuery图片滑块动画
2016/12/09 Javascript
Angular.js之作用域scope'@','=','&'实例详解
2017/02/28 Javascript
jQuery自定义元素右键点击事件(实现案例)
2017/04/28 jQuery
Node学习记录之cluster模块
2017/05/31 Javascript
微信小程序textarea层级过高(盖住其他元素)问题的解决办法
2019/03/04 Javascript
基于Vue实现平滑过渡的拖拽排序功能
2019/06/12 Javascript
JS多个表单数据提交下的serialize()应用实例分析
2019/08/27 Javascript
详解Vue.js 作用域、slot用法(单个slot、具名slot)
2019/10/15 Javascript
复制粘贴功能的Python程序
2008/04/04 Python
python命令行参数sys.argv使用示例
2014/01/28 Python
Python深入学习之闭包
2014/08/31 Python
Python实现简单网页图片抓取完整代码实例
2017/12/15 Python
python中使用ctypes调用so传参设置遇到的问题及解决方法
2019/06/19 Python
python3在同一行内输入n个数并用列表保存的例子
2019/07/20 Python
Python定时任务工具之APScheduler使用方式
2019/07/24 Python
Python的对象传递与Copy函数使用详解
2019/12/26 Python
意大利中国电子产品购物网站:Geekmall.com
2019/09/30 全球购物
中专毕业个人的自荐信格式
2013/09/21 职场文书
工作会议欢迎词
2014/01/16 职场文书
连带责任保证书
2014/04/29 职场文书
乡镇安全生产目标责任书
2014/07/23 职场文书
校园环保广播稿(3篇)
2014/09/15 职场文书
毕业实习指导教师评语
2014/12/31 职场文书
2015年超市员工工作总结
2015/05/04 职场文书
法院答辩状格式
2015/05/22 职场文书
教你用python控制安卓手机
2021/05/13 Python