WEB开发网
开发学院网页设计JavaScript jQuery.API源码深入剖析以及应用实现(3) - 选择器... 阅读

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

 2010-09-14 13:39:27 来源:WEB开发网   
核心提示:在上一篇中还漏了一个框题,jQuery的冲突机制解决方法jQuery.noConflict()以及jQuery.noConflict(extreme),jQuery.API源码深入剖析以及应用实现(3) - 选择器篇(上),这里先分析一下:jQuery.noConflict():运行这个函数将变量$的控制权让渡给第一个

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

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

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

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

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

noConflict:function(deep){
  window.$=_$;
  if(deep)
    window.jQuery=_jQuery;
  returnjQuery;
}

其中_$,_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选择器的原理,主要内容包括:

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

分析

一、基本

1. 【#id】和【element】

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

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

2. 【.class】

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

3. 【*】

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

Sizzle.find=function(expr,context,isXML){
  varset,match;
  if(!expr){
    return[];
  }
  for(vari=0,l=Expr.order.length;i<l;i++){
    vartype=Expr.order[i],match;
    if((match=Expr.match[type].exec(expr))){
      varleft=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 ]的方法,具体实现如下:

varExpr=Sizzle.selectors={
 //…
 find:{
  ID:function(match,context,isXML){
    if(typeofcontext.getElementById!=="undefined"&&!isXML){
      varm=context.getElementById(match[1]);
      returnm?[m]:[];
    }
  },
  NAME:function(match,context,isXML){
    if(typeofcontext.getElementsByName!=="undefined"&&!isXML){
      returncontext.getElementsByName(match[1]);
    }
  },
  TAG:function(match,context){
    returncontext.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
 returnjQuery(context).find(selector);

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

find:function(selector){
  //当表达式不包含“,”符号时候
  if(this.length===1&&!/,/.test(selector)){
    varret=this.pushStack([],"find",selector);
    ret.length=0;
    jQuery.find(selector,this[0],ret);
    returnret;
  }
  //当表达式包含“,”符号时候
  else{
    varelems=jQuery.map(this,function(elem){
      returnjQuery.find(selector,elem);
    });
    returnthis.pushStack(/[^+>][^+>]/.test(selector)?
      jQuery.unique(elems):
      elems,"find",selector);
  }
}

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

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

最关键是在加粗字代码,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(typeofpart==="string"&&!/W/.test(part)){
      part=isXML?part:part.toUpperCase();
      for(vari=0,l=checkSet.length;i<l;i++){
        varelem=checkSet[i];
        if(elem){
          //得到elem的父节点
          varparent=elem.parentNode;
          //如果父节点名称为part值时,在checkSet[i]上赋值父节点,否则赋值false
          checkSet[i]=parent.nodeName===part?parent:false;
        }
      }
    //当part为非单词字符时,如$(".blue>input"),part为“.blue”
    }else{
      for(vari=0,l=checkSet.length;i<l;i++){
        varelem=checkSet[i];
        if(elem){
          checkSet[i]=typeofpart==="string"?
            elem.parentNode:
            elem.parentNode===part;
        }
      }
      if(typeofpart==="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(vari=0,l=checkSet.length;i<l;i++){
      varelem=checkSet[i];
      if(elem){
        //得到elem的前一个节点
        varcur=elem.previousSibling;
        //当cur的节点类型不为元素节点的时候,继续得到cur的前一个节点,否则循环结束
        while(cur&&cur.nodeType!==1){
          cur=cur.previousSibling;
        }
        checkSet[i]=typeofpart==="string"?
          cur||false:
          cur===part;
      }
    }
    if(typeofpart==="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){
    vardoneName="done"+(done++),checkFn=dirCheck;
    if(typeofpart==="string"&&!part.match(/W/)){
      varnodeCheck=part=isXML?part:part.toUpperCase();
      checkFn=dirNodeCheck;
    }
    checkFn("previousSibling",part,doneName,checkSet,nodeCheck,isXML);
  }
}

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

functiondirCheck(dir,cur,doneName,checkSet,nodeCheck,isXML){
  for(vari=0,l=checkSet.length;i<l;i++){
    varelem=checkSet[i];
    if(elem){
      elem=elem[dir];
      varmatch=false;
      while(elem&&elem.nodeType){
        if(elem[doneName]){
          match=checkSet[elem[doneName]];
          break;
        }
        if(elem.nodeType===1){
          if(!isXML)
            elem[doneName]=i;
          if(typeofcur!=="string"){
            if(elem===cur){
              match=true;
              break;
            }
          }elseif(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开始:

while(expr&&set.length){
  for(vartypeinExpr.filter){
    if((match=Expr.match[type].exec(expr))!=null){
      //当type为PSEUDO和POS时,进入该代码块
      varfilter=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;
        }elseif(match===true){
          continue;
        }
      }
      if(match){
        for(vari=0;(item=curLoop[i])!=null;i++){
          if(item){
            found=filter(item,match,i,curLoop);
            varpass=not^!!found;
            if(inplace&&found!=null){
              if(pass){
                anyFound=true;
              }else{
                curLoop[i]=false;
              }
            }elseif(pass){//$("forminput")从这里进
              result.push(item);
              anyFound=true;
            }
          }
        }
      }
     }
  }
}

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

POS:function(elem,match,i,array){
  varname=match[2],filter=Expr.setFilters[name];
  if(filter){
    returnfilter(elem,i,match,array);
  }
}

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

setFilters:{
  first:function(elem,i){
    returni===0;
  },
  last:function(elem,i,match,array){
    returni===array.length-1;
  },
  even:function(elem,i){
    returni%2===0;
  },
  odd:function(elem,i){
    returni%2===1;
  },
  lt:function(elem,i,match){
    returni<match[3]-0;
  },
  gt:function(elem,i,match){
    returni>match[3]-0;
  },
  nth:function(elem,i,match){
    returnmatch[3]-0==i;
  },
  eq:function(elem,i,match){
    returnmatch[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],null,null,curLoop);
        }else{
          varret=Sizzle.filter(match[3],curLoop,inplace,true^not);
          if(!inplace){
            result.push.apply(result,ret);
          }
          returnfalse;
        }
      }elseif(Expr.match.POS.test(match[0])){
        returntrue;
      }
      returnmatch;
    }

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

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

这篇主要讲述 基础,层级,简单的原理,下一篇将讲述选择器剩下的框题 内容,可见性,属性,子元素,表单,表单对象属性等等原理。

系列文章:

jQuery.API源码深入剖析以及应用实现(1) - 核心函数篇

jQuery.API源码深入剖析以及应用实现(2) - jQuery对象访问和数据缓存

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

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

Tags:jQuery API 源码

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接