欢迎光临
我们一直在努力

jQuery源码学习笔记八

今天把jquery的Sizzle选择器引擎讲完。最后给出其大体的工作流程。这东西非常复杂,不要妄图看一遍就明白了。无论看懂与否,多看点源码,还是有裨益的。至少在处理循环结构上有收获吧。

 //@author 司徒正美|なさみ|cheng http://www.cnblogs.com/rubylouvre/ All rights reserved // EXPOSE jQuery.find = Sizzle; jQuery.filter = Sizzle.filter; jQuery.expr = Sizzle.selectors; //以:开头许多都是自定义伪类 jQuery.expr[":"] = jQuery.expr.filters; //css属性display引起的元素不可见 Sizzle.selectors.filters.hidden = function(elem){ return elem.offsetWidth === 0 || elem.offsetHeight === 0; }; //css属性display引起的元素不可见 Sizzle.selectors.filters.visible = function(elem){ return elem.offsetWidth > 0 || elem.offsetHeight > 0; }; //是否在运动中 Sizzle.selectors.filters.animated = function(elem){ return jQuery.grep(jQuery.timers, function(fn){ return elem === fn.elem; }).length; }; //重载jQuery.multiFilter jQuery.multiFilter = function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return Sizzle.matches(expr, elems); }; //把路径上的元素放到结果上,dir为parentNode,previousSibling,nextSilbing jQuery.dir = function( elem, dir ){ var matched = [], cur = elem[dir]; while ( cur && cur != document ) { if ( cur.nodeType == 1 ) matched.push( cur ); cur = cur[dir]; } return matched; }; //在内部调用result好像都为2,dir为previousSibling,nextSilbing //用于子元素过滤 jQuery.nth = function(cur, result, dir, elem){ result = result || 1; var num = 0; //如果cur为undefined中止循环 for ( ; cur; cur = cur[dir] ) if ( cur.nodeType == 1 && ++num == result ) break; return cur; }; //查找不等于elem的兄弟元素节点 jQuery.sibling = function(n, elem){ var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType == 1 && n != elem ) r.push( n ); } return r; }; return; window.Sizzle = Sizzle; 

好了,回头看Sizzle的主程序部分:

 Sizzle.find = function(expr, context, isXML){ var set, match; if ( !expr ) {//如果不是字符串表达式则返回空数组 return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var type = Expr.order[i], match;//按照ID NAME TAG的优先级顺序执行 //这里可以想象一下 //match = "#aaa".exec( /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/) //然后检测match是否为空数组,空数组相当于false if ( (match = Expr.match[ type ].exec( expr )) ) { //ID的正则 /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/ var left = RegExp.leftContext //如果不是一步到位,是复杂的表达式,需要多次查找与筛选 if ( left.substr( left.length - 1 ) !== "\\" ) { //把换行符去掉,得到正常的字段 //如"#id12\ //34" //去掉后,就得到"#id1234" match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { //移除相应部分的表达, // 如#aaa ee,得到ID对应的元素后,把#aaa去掉, //然后用Expr的表达式来匹配剩下的部分 expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { //返回所有后代 set = context.getElementsByTagName("*"); } return {//返回一个对象 set: set, expr: expr }; }; 
 Sizzle.filter = function(expr, set, inplace, not){ var old = expr, result = [], curLoop = set, match, anyFound, isXMLFilter = set && set[0] && isXML(set[0]); while ( expr && set.length ) { for ( var type in Expr.filter ) { //这是Expr.filter中的键值对 //PSEUDO: function(elem, match, i, array){}, //CHILD: function(elem, match){}, //ID: function(elem, match){}, //TAG: function(elem, match){}, //CLASS: function(elem, match){}, //ATTR: function(elem, match){}, //POS: function(elem, match, i, array){} if ( (match = Expr.match[ type ].exec( expr )) != null ) {//match为数组 var filter = Expr.filter[ type ], found, item;//filter这函数 anyFound = false; if ( curLoop == result ) {//如果结果集为空数组,就让result = []; result = []; } if ( Expr.preFilter[ type ] ) { //这是Expr.preFilter中的键值对 //CLASS: function(match, curLoop, inplace, result, not, isXML){}, //ID: function(match){}, //TAG: function(match, curLoop){}, //CHILD: function(match){ }, //ATTR: function(match, curLoop, inplace, result, not, isXML){}, //PSEUDO: function(match, curLoop, inplace, result, not){ }, //POS: function(match){} //preFilter与filter的功能不同,preFilter对字符串进行调整,好让选择器能找到元素 //filter对查找到的元素或元素数组进行筛选 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) {//如果返回的是false anyFound = found = true;//就把anyFound与found标记为true } else if ( match === true ) { continue; } } if ( match ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { //检测元素是否符合要求 found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item );//符合要求就放到结果数组中 anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result;//结果数组将作为一下次要遍历的元素集合返回 } //移除用户输入字符串已查找了的那一部分表达式 expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr == old ) { if ( anyFound == null ) { throw "Syntax error, unrecognized expression: " + expr; } else { break; } } old = expr; } return curLoop; }; 

主程序:

 var Sizzle = function(selector, context, results, seed) { results = results || []; context = context || document; if ( context.nodeType !== 1 && context.nodeType !== 9 ) return [];//context必须为DOM元素或document,要不返回空数组 if ( !selector || typeof selector !== "string" ) { return results;//selector必须存在并且为字符串,否则返回上次循环的结果集 } var parts = [], m, set, checkSet, check, mode, extra, prune = true; // Reset the position of the chunker regexp (start from head) chunker.lastIndex = 0; while ( (m = chunker.exec(selector)) !== null ) { parts.push( m[1] ); if ( m[2] ) { extra = RegExp.rightContext;//匹配内容的右边归入extra break; } } //POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, if ( parts.length > 1 && origPOS.exec( selector ) ) { //处理E F E > F E + F E ~ F if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { //这里的parts[0]肯定不是“”,亦即不会是后代选择器 set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift() if ( Expr.relative[ selector ] ) selector += parts.shift(); set = posProcess( selector, set ); } } } else { var ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) ); set = Sizzle.filter( ret.expr, ret.set ); if ( parts.length > 0 ) { checkSet = makeArray(set); } else { prune = false; } while ( parts.length ) {//倒序的while循环比for循环快 var cur = parts.pop(), pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, isXML(context) ); } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { throw "Syntax error, unrecognized expression: " + (cur || selector); } //数组化NodeList,并加入结果集中 if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context.nodeType === 1 ) { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) {//确保是元素节点 results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, context, results, seed ); if ( sortOrder ) { hasDuplicate = false; results.sort(sortOrder);//重排结果集中的DOM元素,按照原来在网页先后顺序排列 if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) {//确保没有重复的DOM元素,方法比较垃圾 if ( results[i] === results[i-1] ) { results.splice(i--, 1); } } } } } return results; }; 

最后重新说一下其逻辑:

  1. 首先用一个叫chunker的强大正则,把诸如 var str = " #div , h1#id\
    dd.class > span[dd='22222 > 3233'] ul+ li, .class:contain(\"你的+ 999\"),strong span ";这样的字符串,Sizzle称之为selector的东西,分解成一个数组。jQuery源码学习笔记八
  2. 接着对上下文的内容进行判断,确保其为DOM元素或document,否则返回空数组。然后判断selector是否为字符串,由于Sizzle会不断递归调用,selector会越来越短的,直到为零。这些越来越短的selector其实也是第一次chunker 分解的结果之一。不过它们都有可能g再遭分解。每一次循环,这些分解了的字符串都会经过筛选(非空字符),放入parts数组中。
  3. 这些selector最先会判断一下,是否为亲子兄长相邻后代等关系选择器。由于第一次chunker把大部分空白消灭了,造成了一个不幸的结果,把后代选择器也消灭了。因此必须补上后代选择器。详见后面posProcess的“selector + "*"”操作。
  4. 在选择器中,也亦即id,tag,name具有查找能力,在标准浏览器中重载了class部分,让getElementsByClassName也能工作。如果querySelectorAll能工作最好不过,整个Sizzle被重载了。总而言之,Sizzle.find所做的工作比较少,它是按[ "ID", "NAME", "TAG" ]的优先级查找元素的。不过在这之前,先要调用Expr.preFilter把连字符"\"造成的字符串破坏进行修复了。如上面的例子,h1#iddd由于中间的连字符串被切成两个部分,成了数组中的两个元素h1#dd与dd。显然这样查找会找不到dd这个ID,后面查找所有dd元素也是错误的,因此必须把它们重新整合成一个元素h1#dddd。
  5. 根据id,name与tag找到这些元素后,下一个循环就是找它们的子元素或后代元素了,所以Sizzle才会急冲冲地修复后代选择器的问题。至于筛选,Expr有大量的方法来进行。最后是重新排序与去除重复选中的元素,以结果集返回。

机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年

  • 海报
海报图正在生成中...
赞(0) 打赏
声明:
1、本博客不从事任何主机及服务器租赁业务,不参与任何交易,也绝非中介。博客内容仅记录博主个人感兴趣的服务器测评结果及一些服务器相关的优惠活动,信息均摘自网络或来自服务商主动提供;所以对本博客提及的内容不作直接、间接、法定、约定的保证,博客内容也不具备任何参考价值及引导作用,访问者需自行甄别。
2、访问本博客请务必遵守有关互联网的相关法律、规定与规则;不能利用本博客所提及的内容从事任何违法、违规操作;否则造成的一切后果由访问者自行承担。
3、未成年人及不能独立承担法律责任的个人及群体请勿访问本博客。
4、一旦您访问本博客,即表示您已经知晓并接受了以上声明通告。
文章名称:《jQuery源码学习笔记八》
文章链接:https://www.456zj.com/20272.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址