zoukankan      html  css  js  c++  java
  • 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的东西,分解成一个数组。
    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有大量的方法来进行。最后是重新排序与去除重复选中的元素,以结果集返回。
  • 相关阅读:
    零知识证明入门
    Vue入门语法(二)表单,组件,路由和ajax
    Vue入门语法(一)
    okexchain整体架构分析
    写了个unsigned Tx生成二维码的web站点
    metamusk与web3相关资料
    QRCode.js:使用 JavaScript 生成二维码
    js工程安装,发布等命令
    C语言学习笔记-9.结构体
    C语言学习笔记-8.指针
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1608938.html
Copyright © 2011-2022 走看看