首页 > 编程 > JavaScript > 正文

jQuery选择器源码解读(三):tokenize方法

2019-11-20 12:49:34
字体:
来源:转载
供稿:网友
/* * tokenize方法是选择器解析的核心函数,它将选择器转换成两级数组groups * 举例: *  若选择器为“div.class,span”,则解析后的结果为: *  group[0][0] = {type:'TAG',value:'div',matches:match} *  group[0][1] = {type:'CLASS',value:'.class',matches:match} *  group[1][0] = {type:'TAG',value:'span',matches:match} * 由上述结果可以看出,groups的每一个元素以逗号分隔的选择器块的解析结果, * 另外,上述结果中的matches等于模式匹配的结果,由于在此不方便写清楚, * 故只把代码matches:match写在这里。 *  * tokenize方法完成如下两个主要任务: * 1、解析选择器 * 2、将解析结果存入缓存中,以备后用 *  *  * @param selector 待解析的选择器字符串 * @param parseOnly 为true时,说明本次调用是匹配子选择器 *  举个例子:若初始选择器为"div:not(.class:not(:eq(4))):eq(3)" *  代码首先匹配出TAG选择器div, *  之后匹配出的pseudo选择器字符串是:not(.class:not(:eq(4))):eq(3), *  代码会把“.class:not(:eq(4))):eq(3”作为not的括号内的值进一步进行解析, *  此时代码在调用tokenize解析时,parseOnly参数会传入true. */function tokenize(selector, parseOnly) {	var matched, match, tokens, type, soFar, groups, preFilters, 	// 获取缓存中的结果	cached = tokenCache[selector + " "];	/*	 * 若缓存中有selector对应的解析结果	 * 则执行if中语句体	 */	if (cached) {		// 若是对初始选择器解析(parseOnly!=true),则返回缓存结果,		// 若不是,则返回0		return parseOnly ? 0 : cached.slice(0);	}	/*	 * 由于字符串在javascript中不是作为对象来处理的,	 * 所以通过赋值,代码就自动复制了一个新字符串给了soFar,	 * 这样,对soFar的任何处理都不会影响selector的原有数据	 */	soFar = selector;	groups = [];	// 此处赋值,仅仅用于减少后续代码字数,缩短执行路径	preFilters = Expr.preFilter;	while (soFar) {		// Comma and first run		/*		 * rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*")		 * rcomma用来判定是否存在多个选择器块,即用逗号隔开的多个并列的选择器		 * 		 * 下面条件判定依次为:		 * !matched:若是第一次执行循环体,则为true;否则为false。		 *   这里matched即作为是否第一次执行循环体的标识,		 *   也作为本次循环中soFar是否以非法字符串(即非合法单一选择器)开头的标志。		 * (match = rcomma.exec(soFar):获取符合rcomma的匹配项		 */		if (!matched || (match = rcomma.exec(soFar))) {			if (match) {				// Don't consume trailing commas as valid				/*				 * 剔除掉第一个逗号及之前的所有字符				 * 举个例子:				 * 若初始选择器为:"div.news,span.closed",				 * 在解析过程中,首先由后续代码解析完毕div.news,剩下",span.closed"				 * 在循环体内执行到这里时,将逗号及之前之后连续的空白(match[0])删除掉,				 * 使soFar变成"span.closed",继续执行解析过程				 * 				 * 在这里,若初始选择器的最后一个非空白字符是逗号,				 * 那么执行下面代码时soFar不变,即soFar.slice(match[0].length)返回空字符串,				 * 故最终返回的是||后面的soFar				 */				soFar = soFar.slice(match[0].length) || soFar;			}						/*			 * 在第一次执行循环体或者遇到逗号分割符时,将tokens赋值为一个空数组,			 * 同时压入groups数组			 */			groups.push(tokens = []);		}		matched = false;		// Combinators		/*		 * rcombinators = new RegExp(		 *		"^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"),		 * rcombinators用来匹配四种关系符,即>+~和空白		 * 		 * 若soFar中是以关系符开始的,则执行if内的语句体		 */		if ((match = rcombinators.exec(soFar))) {			/*			 * 将match[0]移除match数组,同时将它赋予matched			 * 若原本关系符两边带有空格,则此时match[0]与matched是不相等的			 * 举个例子:			 * 若soFar = " + .div";			 * 执行match = rcombinators.exec(soFar)后,			 * match[0] = " + ",而match[1]="+";			 * 执行完matched = match.shift()后,			 * matched=" + ",而match[0]="+";			 */			matched = match.shift();			// 将匹配结果压入tokens数组中			tokens.push({				value : matched,				// Cast descendant combinators to space				/*				 * rtrim = new RegExp("^" + whitespace + "+|((?:^|[^////])(?:////.)*)"				 *			+ whitespace + "+$", "g"),				 * whitespace = "[//x20//t//r//n//f]";				 * 				 * 下面match[0].replace(rtrim, " ")的作用是将match[0]左右两边的空白替换为空格				 * 但是由于其上的match.shift的作用,match[0]已经是两边不带空白的字符串了,				 * 故此出的替换是没有用途的代码				 */				type : match[0].replace(rtrim, " ")			});						// 将关系符之后的字符串赋予soFar,继续解析			soFar = soFar.slice(matched.length);		}		// Filters		/*		 * 下面通过for语句对soFar逐一匹配ID、TAG、CLASS、CHILD、ATTR、PSEUDO类型的选择器		 * 若匹配到了,则先调用该类型选择器对应的预过滤函数,		 * 然后,将结果压入tokens数组,继续本次循环。		 */		for (type in Expr.filter) {			/*			 * match = matchExpr[type].exec(soFar):对soFar调用type类型的正则表达式对soFar进行匹配,			 *  并将匹配结果赋予match。若未匹配到数据,则match为undefined。			 * !preFilters[type]:若不存在type类型的预过滤函数,则为true			 * match = preFilters[type](match):执行预过滤,并将结果返回给match			 * 			 */			if ((match = matchExpr[type].exec(soFar))					&& (!preFilters[type] || (match = preFilters[type]							(match)))) {				// 将match[0]移除match数组,同时将它赋予matched				matched = match.shift();				// 将匹配结果压入tokens数组中				tokens.push({					value : matched,					type : type,					matches : match				});				// 将匹配结果之后的字符串赋予soFar,继续解析				soFar = soFar.slice(matched.length);			}		}		/*		 * 若matched==false,		 * 则说明本次循环没有有效的选择器(包括关系符和id、class等类型选择器)		 * 因此,解析到当前位置遗留下来的soFar是非法的选择器字符串		 * 跳出while循环体		 */		if (!matched) {			break;		}	}	// Return the length of the invalid excess	// if we're just parsing	// Otherwise, throw an error or return tokens	/*	 * 若不是对初始选择器字符串进行解析(!parseOnly==true),	 *  则返回soFar.length,此时的soFar.length代表连续有效的选择器最终位置,	 *  后续文章将以实例进行说明	 * 若是对初始选择器字符串进行解析,则看soFar是否还有字符,	 *  若是,则执行Sizzle.error(selector)抛出异常;	 *  若不是,则执行tokenCache(selector, groups).slice(0)将结果压入缓存,并返回结果的副本。	 */	return parseOnly ? soFar.length : soFar ? Sizzle.error(selector) :	// Cache the tokens	tokenCache(selector, groups).slice(0);}

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表