jQuery1.11源码分析(5)-----Sizzle编译和过滤阶段[原创]
在上一章中,我们说到在之前的查找阶段我们已经获得了待选集seed,那么这一章我们就来讲如何将seed待选集过滤,以获得我们最终要用的元素。
其实思路本质上还是不停地根据token过滤,但compile这个函数将这些matcher(filter生成的闭包过滤函数)给编译成一个函数(这个效率和我们直接使用过滤函数差不多,关键是在后面),再保存这一个函数,以后遇到同样的selector就可以不用再编译,直接调用就可以了。
接下来我们看看compile的代码
compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
console.log('compile begin');
console.log('arguments:selector, group');
console.log(arguments);
var i,
setMatchers = [],
elementMatchers = [],
cached = compilerCache[ selector + " " ]; if ( !cached ) {
// Generate a function of recursive functions that can be used to check each element
if ( !group ) {
group = tokenize( selector );
}
i = group.length;
while ( i-- ) {
console.log('compile matcherFromTokens '+i);
cached = matcherFromTokens( group[i] );
console.log('compile after matcherFromTokens '+i);
console.log([cached]);
if ( cached[ expando ] ) {
//这里的区别是,setMatchers是当有伪类进行过递归调用Sizzle时出现的多层次的matcher
setMatchers.push( cached );
} else {
elementMatchers.push( cached );
}
} // Cache the compiled function
console.log('compile matcherFromGroupMatchers');
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
console.log('compile after matcherFromGroupMatchers');
console.log([cached]); }
return cached;
};
可以看出,compile实际上就是将不同的tokens用matcherFromTokens编译成一个个matcher(两种不同的matcher,setMatcher和elementMatcher),最后再调用matcherFromGroupMatchers,生成一个superMatcher。
我们接下来看看matcherFromTokens和matcherFromGroupMatchers的源码(注意它是在什么时候把expando加上的,可能还要回到前几篇去看)
function matcherFromTokens( tokens ) {
console.log('matcherFromTokens begin');
console.log('arguments:tokens');
console.log(arguments);
console.log('matcherFromTokens addCombinator');
var checkContext, matcher, j,
len = tokens.length,
leadingRelative = Expr.relative[ tokens[0].type ],
implicitRelative = leadingRelative || Expr.relative[" "],
i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s)
matchContext = addCombinator( function( elem ) {
return elem === checkContext;
}, implicitRelative, true ),
matchAnyContext = addCombinator( function( elem ) {
return indexOf.call( checkContext, elem ) > -1;
}, implicitRelative, true ),
matchers = [ function( elem, context, xml ) {
console.log('matchers 1 begin');
console.log('arguments:elem, context, xml');
console.log(arguments);
return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
(checkContext = context).nodeType ?
matchContext( elem, context, xml ) :
matchAnyContext( elem, context, xml ) );
} ]; for ( ; i < len; i++ ) {
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
console.log('matcherFromTokens addCombinator '+i);
console.log('matcherFromTokens addCombinator elementMatcher '+i);
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
console.log('matcherFromTokens after addCombinator '+i);
console.log(matchers);
} else {
//如果不是连接符
console.log('matcherFromTokens filter '+i);
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
console.log('matcherFromTokens after filter '+i);
console.log(matchers);
// Return special upon seeing a positional matcher
//expando说明什么?
//在上面tokens[i].type为child或者pseudo时,matcher有[expando]
//所以有expando的时候就要加强处理
if ( matcher[ expando ] ) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for ( ; j < len; j++ ) {
if ( Expr.relative[ tokens[j].type ] ) {
break;
}
}
//prefilter,selector,matcher,postFilter,postFinder,postSelector;
//先看这里传入的参数,对于理解setMatcher非常有帮助,它说明matcherFromTokens用了递归的思想,把tokens切割成两部分,已匹配过的和待查找的
console.log('matcherFromTokens setMatcher');
return setMatcher(
i > 1 && elementMatcher( matchers ),
i > 1 && toSelector(
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
).replace( rtrim, "$1" ),
matcher,
i < j && matcherFromTokens( tokens.slice( i, j ) ),
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
j < len && toSelector( tokens )
);
}
matchers.push( matcher );
}
}
console.log('matcherFromTokens elementMatcher');
return elementMatcher( matchers );
}
在matcherFromTokens里用到了三个函数,addCombinator,setMatcher和elementMatcher,后两者的区别在前面的注释中已经有提及了,当不涉及递归等操作时,使用的就是普通的elementMatcher和addCombinator,elementMatcher的代码非常简单,如下
function elementMatcher( matchers ) {
return matchers.length > 1 ?
function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
} :
matchers[0];
}
再看一个短函数addCombinator,增加一个Combinator类型的matcher
function addCombinator( matcher, combinator, base ) {
var dir = combinator.dir,
checkNonElements = base && dir === "parentNode",
doneName = done++;
//有first代表只检查第一个元素
return combinator.first ?
// Check against closest ancestor/preceding element
function( elem, context, xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
return matcher( elem, context, xml );
}
}
} : // Check against all ancestor/preceding elements
function( elem, context, xml ) {
var oldCache, outerCache,
//保证当次dirruns,doneName不变
newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
if ( xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
if ( matcher( elem, context, xml ) ) {
return true;
}
}
}
} else {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || (elem[ expando ] = {});
//这里的outerCache也就是一个对象啊?怎么会有dir属性呢,是后面存进去的
if ( (oldCache = outerCache[ dir ]) &&
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements
return (newCache[ 2 ] = oldCache[ 2 ]);
} else {
// Reuse newcache so results back-propagate to previous elements
//缓存最让我担心的还是失效时机
outerCache[ dir ] = newCache; // A match means we're done; a fail means we have to keep checking
//这里我感觉这个函数的结构设计和matcher是紧耦合的。
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
return true;
}
}
}
}
}
};
}
再来看最长的matcher。。。一定要结合上面调用setMatcher时传入的参数一起看
//这TM又是一个奇葩的函数。
//返回一个添加了expando的函数
//让我难以理解的是filter和matcher这两种类型的函数有什么区别,没区别,filter生成matcher
//第1个参数,preFilter,前置过滤器,相当于“div”过滤器
//第2个参数,selector,前置过滤器的字符串格式,相当于“div”input:checked + p
//第3个参数,matcher,当前位置伪类“:first”的匹配器/过滤器
//第4个参数,postFilter,后置过滤器,相当于“ ”
//第5个参数,postFinder,后置搜索器,相当于在前边过滤出来的集合里边再搜索剩下的规则的一个搜索器
//第6个参数,postSelector,后置搜索器对应的选择器字符串,相当于“input:checked + p”
//伪类选择器时会执行这个函数
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
if ( postFilter && !postFilter[ expando ] ) {
postFilter = setMatcher( postFilter );
}
if ( postFinder && !postFinder[ expando ] ) {
postFinder = setMatcher( postFinder, postSelector );
}
//遇到这种闭包函数,要注意对上面参数的使用
//这里的seed和results有什么区别?
//results用来存已经可以确定返回的元素
return markFunction(function( seed, results, context, xml ) {
var temp, i, elem,
preMap = [],
postMap = [],
preexisting = results.length, // Get initial elements from seed or context
//这里如果没有seed,则获得所有context下的符合selector或*的元素
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization
//这行代码执行完后,matcherIn里剩下的元素是elems里通过preFilter过滤的,preMap存的是过滤通过的元素在原elems里的序号,从小到大
matcherIn = preFilter && ( seed || !selector ) ?
condense( elems, preMap, preFilter, context, xml ) :
elems, matcherOut = matcher ?
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
//??????
postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary
[] : // ...otherwise use results directly
results :
//如果没有matcher,matcherOut就是matcherIn
matcherIn; // Find primary matches
if ( matcher ) {
//????matcher为什么会传入4个参数?看之前的声明只有3个
matcher( matcherIn, matcherOut, context, xml );
} // Apply postFilter
//应用尾过滤
if ( postFilter ) {
//这里temp基本就是不做任何处理拷贝过来啊
temp = condense( matcherOut, postMap );
postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length;
while ( i-- ) {
if ( (elem = temp[i]) ) {
//这个temp果然是中介,把这些没用到的元素再覆盖到原matcherIn,按照postMap从大到小的顺序,再把matcherOut中的这部分设为false
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
}
}
}
//如果还剩seed
if ( seed ) {
if ( postFinder || preFilter ) {
//如果有postFinder?
if ( postFinder ) {
// Get the final matcherOut by condensing this intermediate into postFinder contexts
temp = [];
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) ) {
// Restore matcherIn since elem is not yet a final match
temp.push( (matcherIn[i] = elem) );
}
}
//这是一个递归调用的方式
postFinder( null, (matcherOut = []), temp, xml );
} // Move matched elements from seed to results to keep them synchronized
//这里有一个两个数组互斥的用法
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) &&
(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem);
}
}
} // Add elements to results, through postFinder if defined
} else {
matcherOut = condense(
matcherOut === results ?
matcherOut.splice( preexisting, matcherOut.length ) :
matcherOut
);
if ( postFinder ) {
postFinder( null, results, matcherOut, xml );
} else {
push.apply( results, matcherOut );
}
}
});
}
matcherFromTokens通过调用上面三个函数,然后生成了一个个matchers数组,然后compile再调用matcherFromGroupMatchers把这些matchers合并成一个超级matcher
function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
console.log('matcherFromGroupMatchers begin');
console.log('arguments:elementMatchers, setMatchers');
console.log(arguments);
var bySet = setMatchers.length > 0,
byElement = elementMatchers.length > 0,
superMatcher = function( seed, context, xml, results, outermost ) {
console.log('superMatcher begin');
console.log('arguments:seed, context, xml, results, outermost');
console.log(arguments);
var elem, j, matcher,
matchedCount = 0,
i = "0",
unmatched = seed && [],
setMatched = [],
contextBackup = outermostContext,
// We must always have either seed elements or outermost context
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
// Use integer dirruns iff this is the outermost matcher
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
len = elems.length; if ( outermost ) {
outermostContext = context !== document && context;
} // Add elements passing elementMatchers directly to results
// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
// Support: IE<9, Safari
// Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
if ( byElement && elem ) {
j = 0;
while ( (matcher = elementMatchers[j++]) ) {
if ( matcher( elem, context, xml ) ) {
results.push( elem );
break;
}
}
if ( outermost ) {
dirruns = dirrunsUnique;
}
} // Track unmatched elements for set filters
//???????这里有什么用?
if ( bySet ) {
// They will have gone through all possible matchers
if ( (elem = !matcher && elem) ) {
matchedCount--;
} // Lengthen the array for every element, matched or not
if ( seed ) {
unmatched.push( elem );
}
}
} // Apply set filters to unmatched elements
matchedCount += i;
if ( bySet && i !== matchedCount ) {
j = 0;
while ( (matcher = setMatchers[j++]) ) {
console.log('matcherFromGroupMatchers matcher '+j);
matcher( unmatched, setMatched, context, xml );
console.log('matcherFromGroupMatchers after matcher '+j);
console.log('setMatched');
console.log(setMatched);
console.log('unmatched');
console.log(unmatched);
} if ( seed ) {
// Reintegrate element matches to eliminate the need for sorting
if ( matchedCount > 0 ) {
while ( i-- ) {
if ( !(unmatched[i] || setMatched[i]) ) {
setMatched[i] = pop.call( results );
}
}
} // Discard index placeholder values to get only actual matches
//这里有毛用啊?
setMatched = condense( setMatched );
} // Add matches to results
//注意这里用的是apply
//我TM终于理解为什么会有call和apply这两种语法了。。
push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting
//没有待选元素了,就可以去除结果里重复的元素了
if ( outermost && !seed && setMatched.length > 0 &&
( matchedCount + setMatchers.length ) > 1 ) {
console.log('matcherFromGroupMatchers uniqueSort');
Sizzle.uniqueSort( results );
console.log('matcherFromGroupMatchers after uniqueSort');
console.log(results);
}
} // Override manipulation of globals by nested matchers
if ( outermost ) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
} return unmatched;
}; return bySet ?
markFunction( superMatcher ) :
superMatcher;
}
嗯,拿到这个superMatcher,剩下的就是调用了,最后我们看看传进去的参数(在select中调用)
compile( selector, match )(
seed,
context,
!documentIsHTML,
results,
rsibling.test( selector ) && testContext( context.parentNode ) || context
);
Sizzle源码基本就是这样了,接下来的文章我们会继续分析jQuery其他模块。
jQuery1.11源码分析(5)-----Sizzle编译和过滤阶段[原创]的更多相关文章
- jQuery1.11源码分析(1)-----Sizzle源码概览[原创]
最近在啃jQuery1.11源码,上来就遇到Sizzle这个jQuery的大核心,虽然已经清楚了Sizzle的用途,先绕过去也没事,但明知山有虎偏向虎山行才是我们要做的. 本文面向的阅读对象:正在学习 ...
- jQuery1.11源码分析(2)-----Sizzle源码中的正则表达式[原创]
看完了上篇,对Sizzle有了一个大致的了解,我们接下来就可以正式开始啃Sizzle的源码了.上来就讲matcher难度太大,先来点开胃菜,讲讲Sizzle中的各个正则表达式的作用吧(本来还想讲初始化 ...
- jQuery1.11源码分析(3)-----Sizzle源码中的浏览器兼容性检测和处理[原创]
上一章讲了正则表达式,这一章继续我们的前菜,浏览器兼容性处理. 先介绍一个简单的沙盒测试函数. /** * Support testing using an element * @param {Fun ...
- jQuery1.11源码分析(4)-----Sizzle工厂函数[原创]
在用前两篇讲述完正则表达式.初始化.特性检测之后,终于到了我们的正餐——Sizzle工厂函数! Sizzle工厂函数有四个参数, selector:选择符 context:查找上下文 results: ...
- jQuery1.11源码分析(7)-----jQuery一些基本的API
这篇文章比较繁杂,主要就是把jQuery源码从上到下列出来,看我的注释就好了. jQuery源码对各种加载器做了处理. //阅读这个源码是请先了解一下概念,即时函数,工厂模式 (function( g ...
- jQuery1.11源码分析(6)-----jQuery结构总揽
(在看以下内容之前请先对原型链有一定的了解,比如:prototype是对象还是函数?) 在看jQuery的其他源码之前,必须对jQuery的数据结构有一定的了解. jQuery的核心很简单,jQuer ...
- jQuery1.11源码分析(8)-----jQuery调用Sizzle引擎的相关API
之所以把这部分放在这里,是因为这里用到了一些基本API,前一篇介绍过后才能使用. //jQuery通过find方法调用Sizzle引擎 //jQuery通过find方法调用Sizzle引擎 jQuer ...
- jQuery1.11源码分析(9)-----初始化jQuery对象的函数和关联节点获取函数
这篇也没什么好说的,初始化jQuery对象的函数要处理多种情况,已经被寒冬吐槽烂了.关联节点获取函数主要基于两个工具函数dir和sibling,前者基于指定的方向遍历,后者则遍历兄弟节点(真的不能合并 ...
- jQuery1.11源码分析(10)-----Callbacks模块
Callbacks模块实质上就是一个回调函数队列(当然吹得很牛逼..比如“提供了一种强大的方法来管理回调函数队列”),之所以介绍它是因为后面的Derferred模块基于它. Callbacks生成时接 ...
随机推荐
- [CareerCup] 10.7 Simplified Search Engine 简单的搜索引擎
10.7 Imagine a web server for a simplified search engine. This system has 100 machines to respond to ...
- 20135316王剑桥 linux第十周课实验笔记
关于who 功能说明:显示目前登入系统的用户信息. 语 法:who [-Himqsw][--help][--version][am i][记录文件] 补充说明:执行这项指令可得知目前有那些用户登入系统 ...
- 系统级I/O 第八周11.9~11.15
第十章 系统级I/O cp1 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include ...
- 浪潮之巅IT那点事之三——神奇的规律
“道可道,非常道”是老子在<道德经>中的开篇第一句话,这句话的意思是:万事万物其真理是可以探索并道说得出来的,但这些真理并非是永恒的,天道轮转,没有永恒不变的真理(来自百度百科).在IT行 ...
- HTC Vive 体验的折腾经历
HTC Vive 是个什么东西, 想必我就不用介绍了, 不知道自己百度吧 HTC Vive发布已经有一段时间了, 一直很纠结买还是不买, 这玩意太贵(官网6888),买了还不能直接用, 还要配太高性能 ...
- WCF入门(12)
前言 上次写是小半年前的事情了,还在原来的公司,还在原来的项目,同时认识了不少人.外包公司总是有些不适应的地方,总在很闲和很忙之间徘徊.凌晨2点被客户电话叫醒,只为copy一个文件从一台服务器到另一台 ...
- SqlDependency数据库同步+signalr 推送消息
sqlDependency提供了这样一种能力:当被监测的数据库中的数据发生变化时,SqlDependency会自动触发OnChange事件来通知应用程序,从而达到让系统自动更新数据(或缓存)的目的. ...
- OC----简单的购物系统----
今天下午OC上机考试,虽然考试的时候没写完, 但是课下写完了. main.m #import <Foundation/Foundation.h> #import "Shops.h ...
- Beta冲刺阶段
Beta冲刺阶段 现阶段工作安排以及问题解决 Struts2框架配置 网上下载Struts 2 的框架代码,按照书上教程进行配置 遇到的问题:书上配置过程和实际操作有出入,按照书上过程无法完成配置过程 ...
- Java基础-jdk动态代理与cglib动态代理区别
JDK动态代理 此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑. 代理模式在实际使用时需要指定具体的目标对象 ...