Sizzle的编译过程是调用的Sizzle.compile方法,内部执行了:
matcherFromTokens matcherFromGroupMatchers
把分析关系表,生成用于匹配单个选择器群组的函数。
matcherFromTokens,它充当了selector“分词”与Expr中定义的匹配方法的串联与纽带的作用,可以说选择符的各种排列组合都是能适应的了。Sizzle巧妙的就是它没有直接将拿到的“分词”结果与Expr中的方法逐个匹配逐个执行,而是先根据规则组合出一个大的匹配方法,最后一步执行。
重点就是:
cached = matcherFromTokens(group[i]);
cached的结果就是matcherFromTokens返回的matchers编译函数了。
matcherFromTokens的分解是有规律的:
语义节点+关系选择器的组合
div > p + div.aaron input[type="checkbox"]
Expr.relative 匹配关系选择器类型,当遇到关系选择器时elementMatcher函数将matchers数组中的函数生成一个函数。
在递归分解tokens中的词法元素时,提出第一个typ匹配到对应的处理方法:
matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);
"TAG": function(nodeNameSelector) { var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); return nodeNameSelecto r === "*" ? function() { return true; } : function(elem) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; },
matcher其实最终结果返回的就是bool值,但是这里返回只是一个闭包函数,不会马上执行,这个过程换句话就是编译成一个匿名函数。
继续往下分解:
如果遇到关系选择符就会合并分组了
matchers = [addCombinator(elementMatcher(matchers), matcher)];
通过elementMatcher生成一个终极匹配器:
function elementMatcher(matchers) { //生成一个终极匹配器 return matchers.length > 1 ? //如果是多个匹配器的情况,那么就需要elem符合全部匹配器规则 function(elem, context, xml) { var i = matchers.length; //从右到左开始匹配 while (i--) { //如果有一个没匹配中,那就说明该节点elem不符合规则 if (!matchers[i](elem, context, xml)) { return false; } } return true; } : //单个匹配器的话就返回自己即可 matchers[0]; }
看代码大概就知道,就是分解这个子匹配器了,返回又一个curry函数,给addCombinator方法:
matcher为当前词素前的“终极匹配器”,combinator为位置词素,根据关系选择器检查,如果是这类没有位置词素的选择器:“#id.aaron[name="checkbox"]”,从右到左依次看看当前节点elem是否匹配规则即可。
但是由于有了位置词素,那么判断的时候就不是简单判断当前节点了,可能需要判断elem的兄弟或者父亲节点是否依次符合规则。
这是一个递归深搜的过程。
所以matchers又经过一层包装了,然后用同样的方式递归下去,直接到tokens分解完毕。返回的结果一个根据关系选择器分组后在组合的嵌套很深的闭包函数了,整个函数编译的过程,就结束了,其实整个流程总结就干了那么几件事:
1、在Expr.filter找出每一个选择器类型对应的处理方法
2、从右边往左,向父级匹配的时候。注意词素关系,引入relative记录这个映射的关系
3、把对应的处理函数压入matchers数组
整个编译过程就完成了,其实粗看就是把函数一层一层包装下去,之后通过匹配器传入对应的种子合集seed一层一层的解开。
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://code.jquery.com/jquery-latest.js"></script> <title></title> </head> <body> <button id="test1">模拟编译原理,点击选中checked</button> <div id="text"> <div class="Aaron"> <input type="checkbox" name="readme" /> <input type="checkbox" name="ttt" /> <input type="checkbox" name="aaa" /> <p>Sizzle</p> </div> </div> <script type="text/javascript"> var filter = { ATTR: function(name, operator,check) { return function(elem) { var attr = elem.getAttribute(name) if (operator === "=") { if(attr === check){ return true } } return false; } }, TAG: function(nodeNameSelector) { return function(elem) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeNameSelector; }; } } function addCombinator(matcher) { return function(elem, context, xml) { while ((elem = elem['parentNode'])) { if (elem.nodeType === 1) { //找到第一个亲密的节点,立马就用终极匹配器判断这个节点是否符合前面的规则 return matcher(elem); } } } } 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]; } function matcherFromTokens(tokens){ var len = tokens.length; var matcher, matchers = []; for (i = 0; i < len; i++) { if (tokens[i].type === " ") { matchers = [addCombinator(elementMatcher(matchers), matcher)]; } else { matcher = filter[tokens[i].type].apply(null, tokens[i].matches); matchers.push(matcher); } } return elementMatcher(matchers); } function elementMatcher(matchers) { //生成一个终极匹配器 return matchers.length > 1 ? //如果是多个匹配器的情况,那么就需要elem符合全部匹配器规则 function(elem, context, xml) { var i = matchers.length; //从右到左开始匹配 while (i--) { //如果有一个没匹配中,那就说明该节点elem不符合规则 if (!matchers[i](elem, context, xml)) { return false; } } return true; } : //单个匹配器的话就返回自己即可 matchers[0]; } function compile() { //种子合集 var seed = document.querySelectorAll('input') //选择器 var selector = "Aaron [name=ttt]"; var elementMatchers = []; var results = [] var match = [{ matches: ["div"], type: "TAG", value: "Aaron" }, { type: " ", value: " " }, { matches: ["name", "=", "ttt"], type: "ATTR", value: "[name=ttt]" }] elementMatchers.push(matcherFromTokens(match)); var matcher, elem; for (var i = 0; i < seed.length; i++) { matcher = elementMatchers[0]; var elem = seed[i]; if (matcher(elem)) { results.push(elem); break; } } results[0].checked = 'checked' } $('#test1').click(function() { compile(); }) </script> </body> </html>