• jQuery.API源码深入剖析以及应用实现(3) - 选择器篇(上)


    还漏了一个框题,jQuery的冲突机制解决方法jQuery.noConflict()以及jQuery.noConflict(extreme),这里先分析一下:

    jQuery.noConflict():运行这个函数将变量$的控制权让渡给第一个实现它的那个库。

    jQuery.noConflict(extreme):将$和jQuery的控制权都交还给原来的库。

    比如在prototype框架中的$会和jQuery框架中的$产生命名冲突,这里就是为了解决这种问题。

    现在先看下noConflict方法的具体实现:

    noConflict: function( deep ) {
        window.$ 
    = _$; 

        
    if ( deep )
            window.jQuery 
    = _jQuery; 

        
    return jQuery;
    }

    其中_$,_jQuery是在jQquery源码的开始几行定义的:

    (function(){ 

    var 
           
    // 
        _jQuery = window.jQuery,
        _$ 
    = window.$,
           
    // 
    })();

    他们都是为了防止$被覆盖而将window.jQuery,window.$放在临时变量中保存起来。

    当deep为空的时候,“_$”覆盖“window.$”,“$”的常规功能失效,但jQuery还可以继续使用。当有新的库中重新定义“$”的时候,“jQuery”继续为jQquery的常规功能,而“$”就不是jQuery中的了,它是属于新的库的常规功能;

    当deep不为空的时候,它将“_jQuery”覆盖“window.jQuery”,这样导致可能jQuery插件失效;另外方法返回的jQuery,实际上没有被覆盖。通过它完全可以移到新的一个命名空间,如dom.query = jQuery.noConflict(true); dom.query("div p").hide();


    前言

    这篇文章将介绍jQuery选择器的原理,主要内容包括:

    分析

    一、基本

    1. 【#id】和【element】

     在第一篇中曾经提到核心函数的概念,形如$("#result")【jQuery(expression,[context])】表达式,归根调用【jQuery(elements)】,因此将调用:

    if ( selector.nodeType ) {
        
    this[0= selector;
        
    this.length = 1;
        
    this.context = selector;
        
    return this;
    }

     

    2. 【.class】

    第一篇中曾经提到核心函数的概念,形如 $(".container") 【jQuery(expression,[context])】表达式字符串的实现。

    3. 【*】

    通过第一篇的结论,将代码进行到:

    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;
            
    if ( (match = Expr.match[ type ].exec( expr )) ) {
                
    var left = RegExp.leftContext; 

                
    if ( left.substr( left.length - 1 ) !== """" ) {
                    match[
    1= (match[1|| "").replace(/""/g, "");
                    set 
    = Expr.find[ type ]( match, context, isXML ); 
                    
    if ( set != null ) {
                        expr 
    = expr.replace( Expr.match[ type ], "" );
                        
    break;
                    }
                }
            }
        } 

        
    if ( !set ) {
            set 
    = context.getElementsByTagName("*");
        } 

        
    return {set: set, expr: expr};
    };

    当expr为“*”的时候,根据Expr.match[ type ].exec( expr )为true时,type为TAG,因此将执行:

    if ( left.substr( left.length - 1 ) !== """" ) {
        match[1] = (match[1] || "").replace(/""/g, "");
        set = Expr.find[ type ]( match, context, isXML ); 
        if ( set != null ) {
            expr = expr.replace( Expr.match[ type ], "" );
            break;
        }
    }

    继续查看Expr.find[ type ]的方法,具体实现如下:

    var Expr = Sizzle.selectors = {
      
    //

      find: {
        ID: 
    function(match, context, isXML){
            
    if ( typeof context.getElementById !== "undefined" && !isXML ) {
                
    var m = context.getElementById(match[1]);
                
    return m ? [m] : [];
            }
        },
        NAME: 
    function(match, context, isXML){
            
    if ( typeof context.getElementsByName !== "undefined" && !isXML ) {
                
    return context.getElementsByName(match[1]);
            }
        },
        TAG: 
    function(match, context){
            
    return context.getElementsByTagName(match[1]);
        }
    }

    所以将调用context.getElementsByTagName("*");返回context中所有的DOM元素。


    4. 【selector1,selector2,selectorN】

    参考第一篇中的内容,当表达式包含“,”符号的时候,最后也是返回一个jQuery对象。


    二、层级

    1. 【ancestor descendant】

    在给定的祖先元素下匹配所有的后代元素。

    HTML代码 jQuery代码 结果
    <form>
      <label>Name:</label>
      <input name="name" />
      <fieldset>
          <label>Newsletter:</label>
          <input name="newsletter" />
     </fieldset>
    </form>
    <input name="none" />  
    $("form input") [ <input name="name" />, <input name="newsletter" /> ]

    首先“ancestor descendant” 作为一个表达式字符串,根据第一篇中的内容,它将执行:

    // 处理 形如 $("div .container")的表达式字符串
    else
      
    return jQuery( context ).find( selector );

    接着查看jQuery对象的find方法:

    find: function( selector ) {
        
    // 当表达式不包含“,”符号时候
        if ( this.length === 1 && !/,/.test(selector) ) {
            
    var ret = this.pushStack( [], "find", selector );
            ret.length 
    = 0;
            jQuery.find( selector, 
    this[0], ret );
            
    return ret;
        } 
        
    // 当表达式包含“,”符号时候
        else {
            
    var elems = jQuery.map(thisfunction(elem){
                
    return jQuery.find( selector, elem );
            });

            
    return this.pushStack( /[^+>] [^+>]/.test( selector ) ?
                jQuery.unique( elems ) :
                elems, 
    "find", selector );
        }
    }

    由于jQuery.find = Sizzle; 因此查看Sizzle对象的具体实现:

    Code

    其中set = Sizzle.filter( ret.expr, ret.set );调用Sizzle.filter方法:

    Sizzle.filter = function(expr, set, inplace, not){
        
    var old = expr, result = [], curLoop = set, match, anyFound;

        
    while ( expr && set.length ) {
            
    for ( var type in Expr.filter ) {
                
    if ( (match = Expr.match[ type ].exec( expr )) != null ) {
                    
    var filter = Expr.filter[ type ], found, item;
                    anyFound 
    = false;

                    
    if ( curLoop == result ) {
                        result 
    = [];
                    }

                    
    if ( Expr.preFilter[ type ] ) {
                        match 
    = Expr.preFilter[ type ]( match, curLoop, inplace, result, not );

                        
    if ( !match ) {
                            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 ) {//$("form input")从这里进
                                    result.push( item );
                                    anyFound 
    = true;
                                }
                            }
                        }
                    }

                    
    if ( found !== undefined ) {
                        
    if ( !inplace ) {
                            curLoop 
    = result;
                        }

                        expr 
    = expr.replace( Expr.match[ type ], "" );

                        
    if ( !anyFound ) {
                            
    return [];
                        }

                        
    break;
                    }
                }
            }

            expr 
    = expr.replace(/"s*,"s*/"");

            
    // Improper expression
            if ( expr == old ) {
                
    if ( anyFound == null ) {
                    
    throw "Syntax error, unrecognized expression: " + expr;
                } 
    else {
                    
    break;
                }
            }

            old 
    = expr;
        }

        
    return curLoop;
    };

    最关键是在加粗字代码,result.push( item ); anyFound = true; 和 curLoop = result;将匹配的元素加入result中,然后赋值于curLoop。

    而方法的最后返回的是curLoop。所匹配的元素就是最后所需要的jQuery对象。


    2. 【parent > child】

    在给定的父元素下匹配所有的子元素。

    HTML代码 jQuery代码 结果
    <form>
      <label>Name:</label>
      <input name="name" />
      <fieldset>
          <label>Newsletter:</label>
          <input name="newsletter" />
     </fieldset>
    </form>
    <input name="none" />
    $("form > input") [ <input name="name" /> ]

    我们只要查看一下它的核心代码:

    relative: {
        
    //
        ">"function(checkSet, part, isXML){
            
    // 当part为单词字符时,如$("form > input"),part为“form”
            if ( typeof part === "string" && !/"W/.test(part) ) { 
                part 
    = isXML ? part : part.toUpperCase(); 

                
    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
                    
    var elem = checkSet[i];
                    
    if ( elem ) {
                        
    // 得到elem的父节点
                        var parent = elem.parentNode;
                        
    // 如果父节点名称为part值时,在checkSet[i]上赋值父节点,否则赋值false
                        checkSet[i] = parent.nodeName === part ? parent : false;
                    }
                }
            
    // 当part为非单词字符时,如$(".blue > input"),part为“.blue”
            } else {
                
    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
                    
    var elem = checkSet[i];
                    
    if ( elem ) {
                        checkSet[i] 
    = typeof part === "string" ?
                            elem.parentNode :
                            elem.parentNode 
    === part;
                    }
                } 

                
    if ( typeof part === "string" ) {
                    Sizzle.filter( part, checkSet, 
    true );
                }
            }
        },
    }

    从这里我们可以得到checkSet的值集合。


    2. 【prev + next】

    匹配所有紧接在 prev 元素后的 next 元素。next (Selector) :一个有效选择器并且紧接着第一个选择器。

    例子

    HTML代码 jQuery代码 结果
    <form>
      <label>Name:</label>
      <input name="name" />
      <fieldset>
          <label>Newsletter:</label>
          <input name="newsletter" />
     </fieldset>
    </form>
    <input name="none" /> 
    $("label + input") [ <input name="name" />, <input name="newsletter" /> ]

    只要查看一下它的核心代码:

    relative: {
        
    // 
        "+"function(checkSet, part){ 
            
    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
                
    var elem = checkSet[i];
                
    if ( elem ) {
                    
    // 得到elem的前一个节点
                    var cur = elem.previousSibling;
                    
    // 当cur的节点类型不为元素节点的时候,继续得到cur的前一个节点,否则循环结束
                    while ( cur && cur.nodeType !== 1 ) {
                        cur 
    = cur.previousSibling;
                    }
                    checkSet[i] 
    = typeof part === "string" ?
                        cur 
    || false :
                        cur 
    === part;
                }
            } 

            
    if ( typeof part === "string" ) {
                Sizzle.filter( part, checkSet, 
    true );
            }
        },
        
    // 
    }

    从这里我们可以得到checkSet的值集合。


    3. 【prev ~ next】

    匹配 prev 元素之后的所有 siblings 元素。

    例子

    HTML代码 jQuery代码 结果
    <form>
      <label>Name:</label>
      <input name="name" />
      <fieldset>
          <label>Newsletter:</label>
          <input name="newsletter" />
     </fieldset>
    </form>
    <input name="none" /> 
    <input name="none2" />
    $("form ~ input") [ <input name="none" />, <input name="none2" />]

    只要查看一下它的核心代码:

    relative: {
        
    // 
        "~"function(checkSet, part, isXML){ 
            
    var doneName = "done" + (done++), checkFn = dirCheck; 

            
    if ( typeof part === "string" && !part.match(/"W/) ) {
                
    var nodeCheck = part = isXML ? part : part.toUpperCase();
                checkFn 
    = dirNodeCheck;
            } 

            checkFn(
    "previousSibling", part, doneName, checkSet, nodeCheck, isXML);
        }
    }

    其中checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); 调用的是dirCheck方法,它的具体实现为:

    function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
        
    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
            
    var elem = checkSet[i];
            
    if ( elem ) {
                elem 
    = elem[dir];
                
    var match = false

                
    while ( elem && elem.nodeType ) {
                    
    if ( elem[doneName] ) {
                        match 
    = checkSet[ elem[doneName] ];
                        
    break;
                    } 

                    
    if ( elem.nodeType === 1 ) {
                        
    if ( !isXML )
                            elem[doneName] 
    = i; 

                        
    if ( typeof cur !== "string" ) {
                            
    if ( elem === cur ) {
                                match 
    = true;
                                
    break;
                            } 

                        } 
    else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
                            match 
    = elem;
                            
    break;
                        }
                    } 
                  
    // 由于这里dir为previousSibling,所以这里利用循环不断得到elem的前一个节点,并且赋值checkSet数组
                    elem = elem[dir];
                } 

                checkSet[i] 
    = match;
            }
        }
    }

    从这里我们可以得到checkSet的值集合。



    三、简单

    1. 【:first】,【:last】,【:even】,【:odd】,【 :eq(index) 】,【 :gt(index) 】,【 :lt(index) 】和 【 :not(selector) 】

    :first 匹配找到的第一个元素。

    :last 匹配找到的最后一个元素。

    :even 匹配所有索引值为偶数的元素,从 0 开始计数。

    :odd 匹配所有索引值为奇数的元素,从 0 开始计数。

    :eq(index) 匹配一个给定索引值的元素。

    :gt(index) 匹配所有大于给定索引值的元素。

    :lt(index) 匹配所有小于给定索引值的元素。

    :not(selector) 去除所有与给定选择器匹配的元素。

    例子

    HTML代码 jQuery代码
    <table>
      <tr><td>Header 1</td></tr>
      <tr><td>Value 1</td></tr>
      <tr><td>Value 2</td></tr>
    </table>
    $("tr:first"),$("tr:last"),$("tr:even"),$("tr:odd"),$("tr:eq(1)"),$("tr:gt(0)")

    首先我们看下它的一个正则表达式:

    POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:"(("d*)"))?(?=[^-]|$)/,

    核心代码从Sizzle.filter开始:

    让我们先看下var filter = Expr.filter[ type ],的Expr.filter的具体实现,核心代码为:

    接着看下Expr.setFilters的具体实现:

    setFilters: {
        first: 
    function(elem, i){
            
    return i === 0;
        },
        last: 
    function(elem, i, match, array){
            
    return i === array.length - 1;
        },
        even: 
    function(elem, i){
            
    return i % 2 === 0;
        },
        odd: 
    function(elem, i){
            
    return i % 2 === 1;
        },
        lt: 
    function(elem, i, match){
            
    return i < match[3- 0;
        },
        gt: 
    function(elem, i, match){
            
    return i > match[3- 0;
        },
        nth: 
    function(elem, i, match){
            
    return match[3- 0 == i;
        },
        eq: 
    function(elem, i, match){
            
    return match[3- 0 == i;
        }
    }

    噢,所有的标识 主要在这里判断elem元素在集合中的逻辑位置,并且返回一个布尔值。

    接着 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not );  Expr.prefilter的具体实现,主要核心代码为:

    PSEUDO: function(match, curLoop, inplace, result, not){
                
    if ( match[1=== "not" ) {
                  
                    
    // 代码1
                    if ( match[3].match(chunker).length > 1 ) {
                        match[
    3= Sizzle(match[3], nullnull, curLoop);
                    } 
    else {
                        
    var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
                        
    if ( !inplace ) {
                            result.push.apply( result, ret );
                        }
                        
    return false;
                    }
                } 
    else if ( Expr.match.POS.test( match[0] ) ) {
                    
    return true;
                }
                
    return match;
            }

    当match[1]匹配中包含为“not”时,即表达式字符串中包含:not时,发生“代码1”;否则,根据POS的正则表达式判断返回true。

    最后将匹配的item元素入栈,即 result.push( item )。Sizzle.filter最后返回的是curLoop。所匹配的元素就是最后所需要的jQuery对象。

  • 相关阅读:
    我喜欢的女孩有了男友 :(
    两个月后,我又回来了。
    准备辞职,想看看老板知道我要辞职之后的表情。
    已经交了辞职报告,今天下午跟老板谈一谈。
    上班第十天
    一年了,回来看看。
    上班第十一天
    领到了离职通知单
    对上班失去了兴趣
    还没有拿到回家的火车票,惨了啊。
  • 原文地址:https://www.cnblogs.com/lzhdim/p/1395326.html
Copyright © 2020-2023  润新知