Sizzle一步步实现所有功能(层级选择)
第二步:实现Sizzle("el,el,el..."),Sizzle("el > el"),Sizzle("el el"),Sizzle("el + el"),Sizzle("el ~ el")
(function( window ){
var arr = [];
var select ;
var Expr;
var push = arr.push;
// http://www.w3.org/TR/css3-selectors/#whitespace
// 各种空白待穿正则字符串
var whitespace = "[\\x20\\t\\r\\n\\f]";
// 带空格选择器正则,记忆无空格选择器
// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
var identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+";
// 属性选择器: http://www.w3.org/TR/selectors/#attribute-selectors
var attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
"*\\]";
var rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" );
// 快速选择器正则 ID 或者 TAG(包括*) 或者 CLASS 选择器
var rquickExpr = /^(?:#([\w-]+)|(\w+|\*)|\.([\w-]+))$/;
// 连接符号
var rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" );
// 层级符号正则'>',' ','+','~'
var rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" );
var matchExpr = {
"ID": new RegExp( "^#(" + identifier + ")" ),
"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
"TAG": new RegExp( "^(" + identifier + "|[*])" ),
};
// 浏览器代码正则
var rnative = /^[^{]+\{\s*\[native \w/;
// token缓存
var tokenCache = createCache();
// 编译缓存
var compilerCache = createCache();
// 入口
function Sizzle( selector ){
// 清除空格
selector = selector.replace( rtrim, "$1" )
var results = [];
var match;
var matcher;
var elem;
var m;
var context = document;
// 是否为最简选择器
if( match = rquickExpr.exec( selector )){
// Sizzle('#ID)
if ( (m = match[1]) ) {
elem = context.getElementById( m );
if( elem ){
results.push( elem );
}
return results;
// Sizzle("TAG")
}else if( (m = match[2]) ){
push.apply( results, context.getElementsByTagName( selector ) );
return results;
// Sizzle(".CLASS")
}else if( (m = match[3]) ){
// 支持getElementsByClassName
if( support.getElementsByClassName ){
push.apply( results, context.getElementsByClassName( m ) );
return results;
}
}
}
// 复杂选择调到select
return select( selector, context, results);
}
// 创建缓存函数
function createCache() {
var keys = [];
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
if ( keys.push( key + " " ) > 10 ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key + " " ] = value);
}
return cache;
}
// 错误函数
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};
// 版本支持变量的对外访问入口
var support = Sizzle.support = {};
// 判断是否支持getElementsByClassName
// 支持: IE<9
support.getElementsByClassName = rnative.test( document.getElementsByClassName );
// 表达式对象
// 存放各类相对位置,各种查询函数,各种过滤函数等。
Expr = {
relative: {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
},
filter: {
"TAG": function( nodeNameSelector ) {
var nodeName = nodeNameSelector.toLowerCase();
return nodeNameSelector === "*" ?
function() { return true; } :
function( elem ) {
return elem.nodeName.toLowerCase() === nodeName;
};
},
"CLASS": function( className ) {
var className = className.toLowerCase();
return function( elem ) {
return elem.className.toLowerCase() === className;
};
}
},
find: {
"TAG": function( tag, context ) {
return context.getElementsByTagName( tag );
},
"CLASS": support.getElementsByClassName&&function( tag, context ) {
return context.getElementsByClassName( tag );
},
},
}
// tokenize函数
// 将选择器字符串转化为方便使用的数组对象形式
tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
var cached = tokenCache[ selector + " " ];
// cached.slice生成新的数组,对其修改不会修改其引用缓存
if ( cached ) {
return cached.slice( 0 );
}
// 循环条件
var soFar = selector;
// 结果数组
var groups = [];
// 匹配参数
var matched;
// 一个独立的tokens
var tokens;
// 辅助变量
var match;
while ( soFar ) {
//首次默认创建一个tokens
//之后每碰到一个逗号新增一个新的tokens
if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[0].length ) || soFar;
}
groups.push( (tokens = []) );
}
matched = false;
// 关系token
if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push({
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
});
soFar = soFar.slice( matched.length );
}
// TAG,CLASS,ID token
for ( type in Expr.filter ) {
if ( match = matchExpr[ type ].exec( soFar ) ) {
matched = match.shift();
tokens.push({
value: matched,
type: type,
matches: match
});
soFar = soFar.slice( matched.length );
}
}
// 一次循环到这里三个条件都不符合没有匹配结果时,跳出。
if ( !matched ) {
break;
}
}
// 意外跳出,soFar存在,报错。
return soFar ?
Sizzle.error( selector ) :
// 缓存后转成新数组返回(预防修改缓存内容)
tokenCache( selector, groups ).slice( 0 );
};
// 将tokens转化为selector字符串形式。
function toSelector( tokens ) {
var i = 0,
len = tokens.length,
selector = "";
for ( ; i < len; i++ ) {
selector += tokens[i].value;
}
return selector;
}
// !addCombinator
// 增加关系处理函数
// 返回关系函数,主要功能是,遍历种子节点的关系节点。
// 比如li>a,传入无数个种子节点a,a.parentNode,再执行matcher,matcher里再判断这个父亲节点是不是li
function addCombinator( matcher, combinator ) {
var dir = combinator.dir;
return combinator.first ?
function( elem, context ) {
while( (elem = elem[ dir ]) ){
if ( elem.nodeType === 1 ) {
return matcher( elem, context );
}
}
}:
function( elem, context ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 ) {
if(matcher( elem, context )) {
return true;
}
}
}
return false;
}
}
// !elementMatcher
// 生成matchers遍历器
// matchers数组存放我要过滤的函数,这个函数遍历所有过滤函数,一个不符合就返回false。
function elementMatcher( matchers ) {
return function( elem, context ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context ) ) {
return false;
}
}
return true;
};
}
// !matcherFromTokens
// 根据tokens,生成过滤一组函数matchers,供elementMatcher使用
// 返回的是一个执行所有过滤函数的函数
function matcherFromTokens( tokens ){
var matchers = [];
var matcher;
var i = 0;
var len = tokens.length;
for ( ; i < len; i++ ) {
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
} else {
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
matchers.push( matcher );
}
}
return elementMatcher( matchers );
}
// !matcherFromGroupMatchers
// 返回超级匹配器,
function matcherFromGroupMatchers( elementMatchers ){
// !!最重要superMatcher,也是最核心的函数,其它的函数为它服务。
// 获取种子元素,遍历所有种子元素。
// 遍历elementMatchers
// 符合的推入结果数组
// 一个选择器(逗号隔开的)生成一个elementMatcher,elementMatchers是存放所有elementMatcher的数组
var superMatcher = function( seed, context, results) {
var elems = seed || Expr.find["TAG"]( "*", document );
var len = elems.length;
var i = 0;
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
j = 0;
while ( (matcher = elementMatchers[j++]) ) {
if ( matcher( elem, context) ) {
results.push( elem );
break;
}
}
}
}
return superMatcher;
}
// compile
// 最初的编译器,存放elementMatchers,缓存超级匹配函数并返回
compile = Sizzle.compile = function( selector, match ) {
var i;
var elementMatchers = [];
var cached = compilerCache[ selector + " "];
if ( !cached ) {
i = match.length;
while ( i-- ) {
elementMatchers.push( matcherFromTokens( match[i] ) );
}
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers ));
}
return cached;
}
// select
// 兼容的自写的选择器
select = Sizzle.select = function( selector, context, results){
var token;
var seed;
var tokens;
var find;
var match = tokenize( selector )
if ( match.length === 1 ) {
// tokens
var tokens = match[0].slice( 0 );
// 如果tokens的首项是ID,将其设置为上下文
if ( (token = tokens[0]).type === 'ID' ){
context = document.getElementById(token.matches[0]);
selector = selector.slice( tokens.shift().value.length );
}
// 生成种子seed
// 如"div ul li",所谓种子就是所有的li
// 后面编译函数需要过滤出符合祖先是ul,ul的祖先是div的节点
i = tokens.length;
while ( i-- ){
token = tokens[i];
if ( Expr.relative[ (type = token.type) ] ) {
break;
}
if((find = Expr.find[ type ]))
if( seed = find( token.matches[0],context ) ) {
tokens.splice( i, 1 );
selector = toSelector( tokens )
break;
}
}
};
// 根据selector,match生成superMatcher并调用
compile( selector, match )( seed, context, results );
return results;
}
// 对外入口
window.MSizzle = Sizzle;
})(window)
// 测试
console.log(MSizzle("ul.topnav > li"))
console.log(MSizzle("ul.topnav li"))
console.log(MSizzle("ul.topnav + div"))
console.log(MSizzle("ul.topnav ~ div"))
1.先来整体流程,首先select生成种子seed,然后执行complie编译出超级匹配函数。在complie中,调用matchFromTokens生成每个tokens的匹配函数,如ul>li,div,会生成两个匹配函数,然后存入到elmentMatchers数组。然后,在matchFromTokens,根据tokens,返回匹配函数,elmentMatcher函数,和addCompinator是其辅助函数。然后回到complie中,缓存matcherFromGroupMatchers函数的结果并返回。在matcherFromGroupMatchers的返回superMatcher 函数中,遍历所有种子元素(不存在时的种子元素就是所有节点),利用elmentMatchers数组的匹配函数匹配。符合推入到结果数组中。跳回最初的complie返回的超级匹配函数,传入参数运行。
2.matchFromTokens如何工作。假如我们有tokens,[{type:'CLASS',value:"f"},{type:'CLASS',value:"box"}],seed是一群div节点,我们只要根据tokens生成两个函数,一个matchers数组中,一个函数是当当前节点的className是f时返回true,一个函数是当当前节点的className是box时返回true。外包一个函数elmentMatcher,参数是待匹配节点,执行数组matchers的所有的函数,但凡有一个不匹配直接返回false。
再假设tokens[{type:'TAG',value:'ul'},{type:'>',value:' > '}],seed是一群li节点。这种情况我们需要过滤li的父亲节点是否是ul。这个找到父亲节点的函数就是addCompinator。
3.matcherFromGroupMatchers工作。他是返回一个最终核心匹配函数,这个函数遍历所有节点,每个节点,执行返回elmentMatcher函数,如果返回为true,则存入到结果数组中。由于选择器一般也会包括几个独立的选择器,如ul,div,就是两个elmentMatcher函数存在elmentMatchers,所以还要遍历执行每一个elmentMatcher。
Sizzle一步步实现所有功能(层级选择)的更多相关文章
- Sizzle一步步实现所有功能(基本筛选)
第二步:实现:first,:last,:eq(),even,odd,:gt(),:lt(); :header,:root,:taget; :not(). ;(function( window ){ v ...
- Sizzle一步步实现所有功能(一)
前提: 1.HTML5自带querySelectAll可以完全替代Sizlle,所以我们下面写的Sizzle,是不考虑QSA的. 2.作者考虑了大量兼容情况,比如黑莓4.6系统这样几乎接触不到的bug ...
- 文件夹的层级选择< OC实现 >
类似文件夹的层级选择,可以搜索和创建新文件夹,点击路径标题可以返回对应层级. 界面有点丑,功能还是大概实现了的!! 代码有点多,还是附上地址吧,有兴趣的可以看看哟!!! https://gith ...
- 通用数据水平层级选择控件v0.70升级版使其支持jQuery v1.9.1
升级原因:作者原来脚本支持的jquery版本太低了,查找了下资料,使得它能支持最新版本的jquery 备注说明:脚本代码源作者跟源文出处很难找,只能在此特感谢他的分享. 更新部分: 1.新版本不再支持 ...
- 构建NetCore应用框架之实战篇(三):BitAdminCore框架功能规划选择
本篇承接上篇内容,如果你不小心点击进来,建议从第一篇开始完整阅读,文章内容继承性连贯性. 构建NetCore应用框架之实战篇系列 一.BitAdminCore功能规划 如何选择框架的落地功能,前篇文章 ...
- 多层级Spinner列表选项实时更新树形层级(选择城市)
package com.example.spinnerdemo; import android.os.Bundle; import android.app.Activity; import andro ...
- js中onchange()的使用,实现功能,选择哪一张图片,显示哪一张
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- OWL页面创建Copy功能,把选择内容复制到QC
- sencha touch 带本地搜索功能的selectfield(选择插件)
带本地搜索功能的选择插件,效果图: 在使用selectfield的过程中,数据过大时,数据加载缓慢,没有模糊查询用户体验也不好, 在selectfield的基础上上稍作修改而成,使用方式同select ...
随机推荐
- [Leetcode][Python]46: Permutations
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 46: Permutationshttps://leetcode.com/pr ...
- Linux id 命令 - 显示用户id和组id信息
要登入一台计算机,我们需要一个用户名.用户名是一个可以被计算机识别的身份.基于此,计算机会对使用这个用户名的登陆的人应用一系列的规则.在Linux系统下,我们可以使用 id 命令. 什么是 id 命令 ...
- JQuery获取Checkbox组的值
前台: <div id="addtrtr" style="padding:20px; background-color:#F8F8F8;"> < ...
- C# 3循环 for语句
循环:可以反复执行某段代码,直到不满足循环条件为止. 一.循环的四要素:初始条件.循环条件.状态改变.循环体. 1.初始条件:循环最开始的状态. 2.循环条件:在什么条件下进行循环,不满足此条件,则循 ...
- Fedora 开启 ssh
Fedora 17 已经安装好openssh server了 不用再装 不过默认无开启 首先su root1.开启ssh服务# systemctl start sshd.service 2.随系统一起 ...
- (转)解决JSP路径问题的方法(jsp文件开头path, basePath作用)
在JSP中的如果使用 "相对路径" 则有可能会出现问题. 因为 网页中的 "相对路径" , 他是相对于 "URL请求的地址" 去寻找资源. ...
- jQuery绑定事件-多种方式实现
jQuery绑定事件-多种方式实现: <html> <head> <meta charset="utf-8" /> <script src ...
- AsyncTask 不能与Thread.sleep()同时使用解决方案
public class MainActivity extends Activity { private ImageView iv_ads; String urrstrString = "h ...
- 用include()和ob_get_contents( )方法 生成静态文件
1. 生成静态文件可以在打开缓冲区的前提下,用include()方法去包含要执行的动态文件,这样该动态文件就会在缓冲区中执行,执行完毕后的静态HTML代码就保存在缓冲区中,然后用ob_get_cont ...
- python排序(选择, 插入)
1.选择排序 算法: 对于一组关键字{K1,K2,…,Kn}, 首先从K1,K2,…,Kn中选择最小值,假如它是 Kz,则将Kz与 K1对换:然后从K2,K3,… ,Kn中选择最小值 Kz,再将Kz与 ...