已经写了两篇文章了,个人思路就是按照整个函数的运行顺序进行分析,那么此次就轮到
tokenize
和Sizzle
的主方法了。我仍是会按照原来的方式,先把这两个函数用到的一些内部的属性或者方法先放在最前面,而后在进行分析这两个函数,一些已经出现过的函数或者变量包括以前的正则,我就不重复列举了,不清楚的同窗能够去我以前的文章去查。Sizzle
源码虽然看了很长时间了,可是进度真的好慢,好多东西须要打断点一点点的看,有一些兼容也不是很清楚,要换浏览器去测试。OK,牢骚发到这里,开始撸源码。javascript
这里的全局指的是IIFE中的做用域 css
tokenCache = createCache
tokenize
预编译以后的缓存,下一次若是遇到相同的选择器,直接返回结果nonnativeSelectorCache = createCache
这个原生的querySelectorAll
没法返回结果的选择器字符串的缓存,下次若是出现直接走Sizzle
的方法,再也不走原生方法Expr.prefilter
这东西是因为像attribute pesuod child
的正则,捕获了太多的捕获组,要对这个捕获组进行二次加工。Expr.cacheLength
常量,数字,控制缓存的数量,值是50。createCache
这个方法就是建立缓存function createCache() {
var keys = [];
function cache(key, value) {
//Array.prototype.push返回的是push以后当前数组的长度
if (keys.push(key) > Expr.cacheLength) {
//Array.prototype.shift返回的是被shift出去的值
delete cache[keys.shift()];
}
return ( cache[key + ' '] = value );
}
return cache;
}
复制代码
push
push = arr.push;
slice = arr.slice;
try {
push.apply(
(arr = slice.call(preferredDoc.childNodes)),
preferredDoc.childNodes
);
arr[preferredDoc.childNodes.length].nodeType
} catch(e) {
push = { apply : arr.length ?
function (target, els) {
pushNative.apply(target, slice.call(els))
} :
function (target, els) {
var j = traget.length,
i = 0;
while((traget[i++] = els[i++])){}
traget.legth = j - 1;
}
}
}
复制代码
Expr.prefilter
Expr = {
// ...
prefilter: {
//参数就是被attribute的正则 match 到的数组
/***$1 属性名 $2 运算符 $3 - $5 都是属性值 $3 双引号 $4 单引号 $5 没引号 ***/
'ATTR': function(match) {
// 转码
match[1] = match[1].replace(runescape, funescape);
// 不论是从哪里捕获的都统一放到$3
match[3] = (match[3] || match[4] || match[5] || '').replace(runescape, funescape);
// 若是是 '~=' 的话, 须要留空格
if (match[2] === '~=') {
match[3] = " " + match[3] + " ";
}
return match.slice(0, 4);
},
/*** $1 (only|first|last|nth|nth-last) $2 (child|of-type) $3 括号中的所有内容 $4 even odd 或者 表达式2n+1 中的2n $5 2n的正负 +2n -2n 中的 + - $6 n的倍数 2n 中的 2 $7 运算符 + - $8 最后一个数 1 ***/
'CHILD': function(match) {
match[1] = match[1].toLowerCase();
if (match[1].slice(0, 3) === 'nth') {
// nth得有参数
if(!match[3]) {
Siizle.error(match[0]);
}
match[4] = +(match[4] ?
match[5] + (match[6] || 1) :
2 * (match[3] === 'even' || match[3] === 'odd'));
match[5] = +((match[7] + match[8]) || match[3] === 'odd');
// 除了nth的其他的没有括号中的值
} else if (match[3]) {
Sizzle.error(match[0]);
}
return match;
},
/*** $1 伪类名 $2 括号中的所有内容 $3 在引号中的值 $4 单引号的值 $5 双引号的值 $6 没有引号的值 ***/
'PESUDO': function(match) {
var excess,
// $6没有值而$2有值的状况: :not(:nth-child(2))
unquoted = !match[6] && match[2];
if (matchExpr['CHILD'].test(match[0])) {
return null;
}
// 若是是引号中的内容
if (match[3]) {
match[2] = match[4] || match[5] || '';
// 若是括号中的内容仍是个伪类
} else if (unquoted && repseudo.test(unquoted) &&
//递归调用tokenize
(excess = tokenize(unquoted, true)) &&
// 留最近的一个()
// excess是个负数
(excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) {
match[0] = match[0].slice(0, excess);
match[2] = unquoted.slice(0, excess);
}
return match.slice(0, 3);
}
}
// ...
}
复制代码
function testContext( context ) {
return context && typeof context.getElementsByTagName !== "undefined" && context;
}
复制代码
Sizzle
在1.7
以后加入了预编译的思想,其实你们都这么说,我就也跟着这么说了。我理解的预编译其实就是将所输入的东西,经过某种规则,进行转换,转换成另外一种格式。这也有另外一种说辞,也就是AST
,我想这个词你们应该更清楚一点,不一样AST
是什么的同窗能够去看一下这篇文章。java
Sizzle
中的预编译就是tokenize
函数,它将各类选择器按照转换成了一个对象,举个简单的例子node
var selector = 'a#link > p, .expando';
// 会转换成一下的样子
var tSelector = [
[
{
type: 'TAG',
value: 'a',
match: ['a']
},
{
type: 'ID',
value: '#link',
match: ['link']
},
{
type: '>'
value: '>'
},
{
type: 'TAG',
value: 'p',
match: ['p']
}
],
[
{
type: 'CLASS',
value: '.expando',
match: ['expando']
}
]
]
复制代码
tokenize = Sizzle.tokenize = function(selector, parseOnly) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[selector + ' '];
// 有没有缓存,若是有缓存 那么直接返回就ok
if (cached) {
// 若是是parseOnly的话, 缓存里面存的都是匹配上的字符,
// 因此若是有的话, 那么不会有剩余的字符串因此返回0
// 在返回的时候 会返回一个缓存的浅复制.
return parseOnly ? 0 : cached.silce(0)
}
soFar = selector;
groups = [];
preFilters = Expr.preFilter;
while (soFar) {
// 若是是第一次进入, 或者是匹配到了逗号
if (!matched || (match = rcomma.exec(soFar))) {
// 若是是逗号的话 把逗号去了
if (match) {
soFar = soFar.slice(match[0].length) || soFar;
}
groups.push( (tokens = []) );
}
matched = false;
// 若是是关系选择器
if ( (match = rcombinators.exec(soFar))) {
matched = match.shift();
tokens.push({
value: matched,
type: match[0].replace(rtrim, " ")
});
soFar = soFar.slice(0, matched.length);
}
// 循环匹配
// TAG CLASS ID ATTR CHILD PESUDO
for (type in Expr.filter) {
if (match = matchExpr[type] && (!preFilter[type] || (match = preFilter[type](match)))) {
matched = match.shift();
tokens.push({
value: matched,
type: type,
matches: match
});
soFar = soFar.slice(matched.length);
}
}
if (!matched) {
break;
}
}
// 这里也是, 若是是parseOnly的话就返回没匹配到的剩下的字符串的长度
// 若是不是的话, 就要看有没有剩下的字符串
// 若是有, 那说明字符串不合法 直接报错
// 若是没有的话 先存缓存, 而后再return一个副本出去
return paresOnly ?
soFar.length :
soFar ?
Sizzle.error(selector) :
tokenCache(selector, groups).slice(0);
}
复制代码
这里会出现一种状况,好比选择字符串是':not(:nth-child(2))'
这样的,进入了tokenize
,这个时候到PESUDO
,那么就要走preFilter['PESUDO']
方法,这个字符串被匹配的时候是$2
有值而$6
没值的状况,因此会再次走tokenize函数(paresOnly = true)
,造成递归。数组
Sizzle
函数就是我们调用选择器走的第一个函数,前两篇文章说的方法都是在引入Sizzle
时运行的。该方法会判断字符串是不是简单的class tag id
。好比p
,#id
,.container
。或者是否能够直接使用querySelectorAll
,若是都不能够的话,就进入select
方法。浏览器
function Sizzle(selector, context, results, seed) {
var m, i, elem, nid, match, groups, newSelector,
newContext = context && context.ownerDocument,
// 若是不传context, 那么默认就是9
nodeType = context ? context.nodeType : 9;
results = results || [];
// 若是选择器是空, 或者选择器不是字符串, 又或者 context的节点类型不是 element document documetfragment之中的任何一个的话 直接返回[]
if (typeof selector != 'string' || !selector ||
nodeType !== 1 && nodeType !== 9 && noedeType !== 11) {
return results;
}
if (!seed) {
// 设置一次document可是通常都是直接返回了
setDocument(context);
context = context || doucment;
if (documentIsHtml) {
//若是是简单的选择id tag class这种状况
if (nodeTpe !== 11 && (match = rquickExpr.exec(selector))) {
//ID
if (m = match[1]) {
// 是document节点
if (nodeType === 9) {
if ( (elem = context.getElementById(m)) ) {
if (elem.id === m) {
results.push(elem);
return results;
}
} else {
return results;
}
// 若是是元素节点
// 元素节点是没有getElememtById这个方法的只能经过document选,选完元素以后判断是否是被当前元素节点包含
} else {
if (newContext && (elem = newContext.getElementById(m)) &&
contains(context, elem) &&
elem.id === m) {
results.push(elem);
return results;
}
}
// 标签选择
} else if (match[2]) {
// 因为rquickExpr不会匹配到(*),因此不须要考虑兼容问题, 直接返回全部匹配到的元素就能够了
push.apply(results, context,getElementByTagName(selector));
// class 选择
} else if ((m = match[3]) && support.getElementsByClassName &&
context.getElementsByClassName) {
push.apply(results, context,getElementsByClassName(m));
return results;
}
}
// 若是支持querySelectorAll, 而且这个字符串并非以前出现过的没法用原生方法匹配的字符串, 而且没有兼容问题中没有这类的话
if (support.qsa &&
!nonnativeSelevtorCache[selector + ' '] &&
(!rbuggyQSA || !rbuggyQSA.test(selector)) &&
// 排除object对象
(nodeType!==1 || context.nodeName.toLowerCase() !== 'object')) {
newSelector = selector;
newContext = context;
// querySelector有一个问题,若是不是document.querySelectorAll而是element.querySelectorAll的话
// 当选择字符串出现了关系选择符也就是 '> ~ + ,' 这些的话,选择出来的结果会有出入
// 解决这个问题的方法就是给当前的element加一个id 再在本来的选择字符串的最前面添加这个id
// 经过doucment.querySelectorAll选择元素
if (nodeType === 1 &&
(rdescend.test(selector) || rcombinators.test(selector))) {
// 若是是兄弟选择器的话 那么要给其父集添加id
newContext = rsibling.test(selector) && testContext(context.parentNode) ||
context;
// 这里这个判断不是很清楚 若是有人知道是什么意思的话但愿能解答一下蟹蟹
// 如下是英文注释
// We can use :scope instead of the ID hack if the browser
// supports it & if we're not changing the context.
if (newContext !== context || !support.scope) {
if ((nid = context.getAttribute("id"))) {
nid = nid.replace(rcssescape, fcssescape);
} else {
context.setAttribute('id', (nid = expando));
}
}
groups = tokenize(selector);
i = groups.length;
while(i--) {
groups[i] = (nid? '#' + nid : ':scope') + ' ' +
toSelector(groups[i]);
}
newSelector.groups.join(',');
}
try {
push.apply(results,
newContext.querySelectorAll(newSelector)
);
return results;
} catch(e) {
// 推入原生方法不能解析的字符串缓存
nonnatvieSelectorCache(selector, true);
} finally {
if (nid === expando) {
context.removeAttribute('id');
}
}
}
}
}
// 全部不行的都走select方法
return select(selector.replace(rtrim, '$1'), context, results, seed);
}
复制代码
到此Sizzle
的前置函数应该就都看完了, 在以后应该就是如何选择元素了。下一章应该会写select
方法吧,就这样。缓存