• jquery 源码分析七


         这篇主要是分析Sizzle引擎中最关键的一个函数Sizzle,这个函数接受四个参数selector,context,results,seed,这四个参数的意义分别是选择器,上下文环境,保存结果数组,还有一个未知。。

         由于这个函数里面涉及了许多相关函数,所以先对主函数进行分析,并只是列出相关函数的作用。相关函数的分析会放在文章末尾。接下来先看主函数:

      1 function Sizzle( selector, context, results, seed ) {
      2     var match, elem, m, nodeType,
      3         i, groups, old, nid, newContext, newSelector;
      4         //各种常量定义
      5     //设置合理的root节点,在网页中为document
      6     if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
      7         setDocument( context );
      8     }
      9     //确定上下文环境和结果数组
     10     context = context || document;
     11     results = results || [];
     12     //如果无selector或者selector不为字符串,则直接返回results
     13     if ( !selector || typeof selector !== "string" ) {
     14         return results;
     15     }
     16     //如果上下文环境的节点不是element或者document的话,就直接返回 空数组
     17     if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
     18         return [];
     19     }
     20 
     21     //documentIsHTML 用于确定工作环境是否是html
     22     if ( documentIsHTML && !seed ) {
     23 
     24         // rquickExpr = /^(?:#([w-]+)|(w+)|.([w-]+))$/
     25         // 用于快速的检测是否是单一的ID,CLASS或TAG选择符
     26         if ( (match = rquickExpr.exec( selector )) ) {
     27             // ID选择
     28             if ( (m = match[1]) ) {
     29                 if ( nodeType === 9 ) {
     30                     elem = context.getElementById( m );
     31                     // 检测是否有parentNode存在,有些浏览器会返回已经被删除了的节点
     32                     if ( elem && elem.parentNode ) {
     33                         // 处理在IE, Opera, 和Webkit 会返回name等于 m 的元素
     34                         if ( elem.id === m ) {
     35                             results.push( elem );
     36                             return results;
     37                         }
     38                     } else {
     39                         return results;
     40                     }
     41                 } else {
     42                     // 如果context不是document的话,要检测context对elem的包含情况。
     43                     if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
     44                         contains( context, elem ) && elem.id === m ) {
     45                         results.push( elem );
     46                         return results;
     47                     }
     48                 }
     49 
     50             // TAG选择
     51             } else if ( match[2] ) {
     52                 push.apply( results, context.getElementsByTagName( selector ) );
     53                 return results;
     54 
     55             // CLASS选择
     56             } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
     57                 push.apply( results, context.getElementsByClassName( m ) );
     58                 return results;
     59             }
     60         }
     61 
     62         // querySelectorAll,处理复杂的选择
     63         // rbuggyQSA用于检测querySelectAll的怪异特性
     64         if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
     65             //expando是sizzle中唯一特征码
     66             nid = old = expando;
     67             newContext = context;
     68             newSelector = nodeType === 9 && selector;
     69 
     70             
     71             // querySelectorAll在element为根元素时行为很奇怪
     72             // 我们可以给根元素一个特定的ID值
     73             // 这样在selector最前面加上ID,就可以做到很好的筛选
     74             // IE8中不支持在对象元素上用
     75             if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
     76                 //tokenize的作用是将selector中的筛选符分割开来并放到相应数组中
     77                 //例:tokenize('div#a')
     78                 // --->[[
     79                 //      {matches:{0:div,index:0,input:'div#a'},
     80                 //       type:'TAG',
     81                 //       value:'div'},
     82                 //       {matches:{0:a,index:0,input:'div#a'},
     83                 //       type:'ID',
     84                 //       value:'#a'}
     85                 //      ]]
     86                 groups = tokenize( selector );
     87 
     88                 //如果context存在id,那么就是用原来的id,否则就用expando
     89                 if ( (old = context.getAttribute("id")) ) {
     90                     nid = old.replace( rescape, "\$&" );
     91                 } else {
     92                     context.setAttribute( "id", nid );
     93                 }
     94                 nid = "[id='" + nid + "'] ";
     95 
     96                 //将所有的选择符拼接
     97                 i = groups.length;
     98                 while ( i-- ) {
     99                     groups[i] = nid + toSelector( groups[i] );
    100                 }
    101                 //探测是否有a+div或者[class~='abc']类似的情况,rsibling = /[+~]/
    102                 newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
    103                 newSelector = groups.join(",");
    104             }
    105             //newSelector存在的话
    106             if ( newSelector ) {
    107                 try {
    108                     //直接在context下用querySelectorAll,并将结果压入数组
    109                     push.apply( results,
    110                         newContext.querySelectorAll( newSelector )
    111                     );
    112                     return results;
    113                 } catch(qsaError) {
    114                 } finally {
    115                     //最后删除添加的id
    116                     if ( !old ) {
    117                         context.removeAttribute("id");
    118                     }
    119                 }
    120             }
    121         }
    122     }
    123 
    124     // 其它情况都用select,这个函数以后分析
    125     return select( selector.replace( rtrim, "$1" ), context, results, seed );
    126 }

    接下来就是对里面那些相关函数的分析,这里面的涉及到的函数或变量主要有:setDocument,documentIsHTML,contains,toSelector,testContext,rbuggyQSA,tokenize

    rbuggyQSA,tokenize,setDocument因为太绕(我连开5,6个jquery一起读了,然后再用chrome来追踪才读出来的,泪奔。。。),所以这次先不讲了。

    首先是documentIsHTML,废话不多说,直接上代码:

    1 documentIsHTML = !isXML( doc );
    1 isXML = Sizzle.isXML = function( elem ) {
    2     // 首先是确定根元素是否有documentElement
    3     // 然后是确定documentElement节点名字是不是HTML
    4     var documentElement = elem && (elem.ownerDocument || elem).documentElement;
    5     return documentElement ? documentElement.nodeName !== "HTML" : false;
    6 };

    然后是contains,contains是在setDocument中定义的,因为contains是Sizzle中的全局变量,所以就可以到处用了。

     1     // rnative = /^[^{]+{s*[native w/用于检测是否有原生函数
     2     // docElem = 根元素.documentElement
     3     // hasCompare = rnative.test( docElem.compareDocumentPosition );
     4     contains = hasCompare || rnative.test( docElem.contains ) ?
     5         //首先是有compareDocumentPosition或者contains的情况
     6         function( a, b ) {
     7             var adown = a.nodeType === 9 ? a.documentElement : a,
     8                 bup = b && b.parentNode;
     9             //先判断是否a直接包含b.parentNode
    10             //否则,在确定bup后,先尝试用contains
    11             //再用compareDocumentPosition
    12             //compareDocumentPosition返回16表示a包含bup
    13             //故要用&16将其转化为0或非0数字
    14             return a === bup || !!( bup && bup.nodeType === 1 && (
    15                 adown.contains ?
    16                     adown.contains( bup ) :
    17                     a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
    18             ));
    19         } :
    20         //b循环向上取parent,若与a相同,则返回true
    21         function( a, b ) {
    22             if ( b ) {
    23                 while ( (b = b.parentNode) ) {
    24                     if ( b === a ) {
    25                         return true;
    26                     }
    27                 }
    28             }
    29             //循环不到,就返回false
    30             return false;
    31         };

    然后是toSelector:

     1 //循环,将每个对象下的value值合并到selector
     2 function toSelector( tokens ) {
     3     var i = 0,
     4         len = tokens.length,
     5         selector = "";
     6     for ( ; i < len; i++ ) {
     7         selector += tokens[i].value;
     8     }
     9     return selector;
    10 }

    对于selector有空格的情况,toknize函数解析的时候会将空格也独立解析成一项,所以在这边合并时空格仍旧会出现,不会被丢弃。

    最后是testContext,检测context是否为空,并且context下是否有getElementsByTagName方法,代码如下:

    1 function testContext( context ) {
    2     return context && typeof context.getElementsByTagName !== strundefined && context;
    3 }

    呼,搞定~

  • 相关阅读:
    集RTMP, HLS, FLV, WebSocket 于一身的网页直播/点播播放器EasyPlayer.js引用videojs无法自动播放问题解决
    HLS播放器RTSP播放器支持8K播放且低延时高并发全功能流媒体播放器EasyPlayer搭建之HTML中 px,em,rem该如何区别?
    网页全终端安防视频流媒体播放器EasyPlayer.js如何实现在web浏览器播放H.265编码视频?
    网页全终端视频流媒体视频直播/点播播放器EasyPlayer.js实现WEB播放H265/HEVC视频方案介绍
    网页全终端视频流媒体播放器EasyPlayer之使用 nginx 和 rtmp 插件搭建视频直播和点播服务器
    车辆实时监控项目中数字摄像头和模拟摄像头的运用、区别及优势分析
    音视频流媒体服务器直播点播平台在车辆实时监控系统中如何做用户观看限制?
    如何借助CDN解决在线教育带宽小、访问大、网点分布不均等问题
    关于区块链,看看诺奖得主是如何评说的!
    区块链也有随机性!
  • 原文地址:https://www.cnblogs.com/cyITtech/p/3598410.html
Copyright © 2020-2023  润新知