Jquery 实现原理深入学习(4)

前言

1.总体结构 √

2.构建函数 √

3.each功能函数实现 √

4.map功能函数实现 √

5.sizzle初步学习 √

6.attr功能函数实现

7.toggleClass功能函数实现(好伤)

8.val功能函数实现

9.ajax异步请求以及扩展学习

正文

今天学习sizzle,这是一个非常大的命题。以目前的水平只能浅尝则止。

sizzle是一款纯js实现的css选择权引擎。而选择器这个功能是Jquery中用的最多的部分。

看下官方文档

Sizzle supports virtually all CSS 3 Selectors, including escaped selectors (.foo\+bar), Unicode selectors, and results returned in document order. The only exceptions are those that would require additional DOM event listeners to keep track of the state of elements.

sizzle支持几乎所有css3选择器,并按照文档位置返回结果。一些深层次的我们不会去探讨,主要研究这几个小内容.

1.引擎入口
2.match方法
3.find方法
4.getText方法

直接跳到sizzle函数

//选择器入口,查找与选择器表达式selector匹配的元素集合
function Sizzle( selector, context, results, seed ) {
//selector:css选择器表达式.
//context:上下文环境.
//results:可选的数组或类数组,sizzle将把查找到的元素添加到其中.
//seed:可选的元素集合.函数sizzle将从该元素集合中过滤出匹配选择器表达式的元素集合.

	var match, elem, m, nodeType,
		// QSA vars
		i, groups, old, nid, newContext, newSelector;

	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
		setDocument( context );

	}

	context = context || document; //如果未传入context,则默认为当前document对象.
	results = results || [];//如果未传入参数results,则默认为空数组[].
	nodeType = context.nodeType; //判断上下文节点类型。nodetype具体请看下面。

	if ( typeof selector !== "string" || !selector ||
		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
//如果参数selector是空字符串,或者不是字符串,节点类型不是元素节点或者document节点或者DOCUMENT_FRAGMENT节点则直接返回results.
注意在1.7版本它是分selector和nodeType两种情况返回的,最新版直接一起判断了

		return results;
	}

	if ( !seed && documentIsHTML ) {
 // 尽可能快地找到目标节点, 选择器类型是id,标签和类
		if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
		   // rquickExpr.exec 看下文
			// Speed-up: Sizzle("#ID")
			if ( (m = match[1]) ) { //把正则结果存到m中
				if ( nodeType === 9 ) { 这里才是真正判断节点是不是元素节点
					elem = context.getElementById( m );
              
              // 检查Blackberry 4.6返回的已经不在document中的parentNode
		
					if ( elem && elem.parentNode ) {
						// IE, Opera, Webkit有时候会返回name == m的元素
						if ( elem.id === m ) {
							results.push( elem );
							return results;
						}
					} else {
						return results;
					}
				} else {
					// 上下文不是document
					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
						contains( context, elem ) && elem.id === m ) {
						results.push( elem );
						return results;
					}
				}

			// Speed-up: Sizzle("TAG")
			} else if ( match[2] ) {
				push.apply( results, context.getElementsByTagName( selector ) );
				return results;
          
			// Speed-up: Sizzle(".CLASS")
			} else if ( (m = match[3]) && support.getElementsByClassName ) {
				push.apply( results, context.getElementsByClassName( m ) );
				return results;
			}
		}
    
		// QSA path
		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
			nid = old = expando;
			newContext = context;
			newSelector = nodeType !== 1 && selector;

			      // 使用QSA, QSA: querySelectorAll, 原生的QSA运行速度非常快,因此尽可能使用QSA来对CSS选择器进行查询
          // querySelectorAll是原生的选择器,但不支持老的浏览器版本, 主要是IE8及以前的浏览器
          // rbuggyQSA 保存了用于解决一些浏览器兼容问题的bug修补的正则表达式
          // QSA在不同浏览器上运行的效果有差异,表现得非常奇怪,因此对某些selector不能用QSA
          // 为了适应不同的浏览器,就需要首先进行浏览器兼容性测试,然后确定测试正则表达式,用rbuggyQSA来确定selector是否能用QSA			// IE 8 doesn't work on object elements
			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
				groups = tokenize( selector );

				if ( (old = context.getAttribute("id")) ) {
					nid = old.replace( rescape, "\\$&" );
				} else {
					context.setAttribute( "id", nid );
				}
				nid = "[id='" + nid + "'] ";

				i = groups.length;
				while ( i-- ) {
					groups[i] = nid + toSelector( groups[i] );
				}
				newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
				newSelector = groups.join(",");
			}

			if ( newSelector ) {
				try {
					push.apply( results,
						newContext.querySelectorAll( newSelector )
					);
					return results;
				} catch(qsaError) {
				} finally {
					if ( !old ) {
						context.removeAttribute("id");
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

函数Sizzle执行的6个关键步骤如下:
1.解析选择器表达式,解析出块表达式和关系符.
2.如果存在位置伪类,则从左向右查找:
a.查找第一个块表达式匹配的元素集合,得到第一个上下文元素集合.
b.遍历剩余的块表达式和块间关系符,不断缩小上下文元素集合.
3.否则从右向左查找:
a.查找最后一个块表达式匹配的元素集合,得到候选集,映射集.
b.遍历剩余的块表达式和块间关系符,对映射集执行块间关系过滤.
4.根据映射集筛选候选集,将最终匹配的元素放入结果集.
5.如果存在并列选择器表达式,则递归Sizzle()查找匹配的元素集合,并合并,排序,去重.
6.最后返回结果集.

根据以上提示我们往回走看源码。

期间我们发现了一个Nodetype的东东,一开始我以为是jquery自己定义,后发翻遍源码发现并不是,那么应该是原生的,google了一下在mdn找到了详细解释。

概述
返回一个整数值,代表当前节点的节点类型.

语法
var type = node.nodeType;
type的值可能如下:

常量名	值
ELEMENT_NODE	1
ATTRIBUTE_NODE	2
TEXT_NODE	3
CDATA_SECTION_NODE	4
ENTITY_REFERENCE_NODE	5
ENTITY_NODE	6
PROCESSING_INSTRUCTION_NODE	7
COMMENT_NODE	8
DOCUMENT_NODE	9
DOCUMENT_TYPE_NODE	10
DOCUMENT_FRAGMENT_NODE	11
NOTATION_NODE	12

然后一路走下来,发现 match = rquickExpr.exec( selector ))

来看看它是做什么的。

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

一个解析选择器类型的正则表达式。

然后我们一路走下来发现其实之前的6个关键提示一点都没用-0- 然而我们并不打算深入词法什么的,因此直接跳到getText

getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {
		// 如果不是节点,那么假设它是一个数组
		while ( (node = elem[i++]) ) {
			// 跳过注释节点
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
		// 对元素使用textContent textContent 属性设置或返回指定节点的文本内容,以及它的所有后代。
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent; //如果是字符串就返回
		} else {
			// 遍历其子节点
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem ); //递归
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue; //如果是text节点或者CDATA_SECTION节点 那么直接返回其值
	}
	// 不包括注释或处理指令节点

	return ret;
};

我们可以发现 仅仅只是一个获取text内容,jquery就已经写的很细很细。希望以后有能力把sizzle里真正精髓的部分学习掉。

今天就到这里。

Table of Contents