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

 前言

 Jquery(http://jquery.com/)是一个轻量级,快速简洁的Javascript框架,它的容量小巧,简洁和简短的语法, 容易记;用户能更方便地处理HTML DOM、Events、实现动画效果,并且提供Ajax的支持。目前最新版本为jQuery 1.3.1(http://jqueryjs.googlecode.com/files/jquery-1.3.1.js),这系列文章将对该版本的源码进行阐述。

  现在开始本系列的第一篇,Jquery核心函数,内容主要包括:

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

  分析

  1. 在Jquery的应用开发中,我们经常看到这样的代码:

$("div.container").css("display","none");//将div节点下样式值为container的节点设置为不显示
varwidth=$("div.container").width();//得到div节点下样式值为container的宽度
varhtml=$(document.getElementById("result")).html();//得到id为result的节点的innerHTML值
$("#result",document.forms[0]).css("color","red");//将在第一个Form节点下id为result的字体颜色设置为红色
$("<div>hello,world</div>").appendTo("#result");//将HTML字符串信息内部追加到id为result的节点末尾
 

  那么$(...)里面的参数,Jquery API中是怎样辨认参数是表达式,id,HTML字符串,还是DOM元素呢?

  现在我们来深入剖析Jquery源码。

 

  2. 这里,我先来做个测试,我将Jquery API简化为这样的代码:

(function(){
  varwindow=this,
  
  jQuery=window.jQuery=window.$=function(selector,context){
    returnnewjQuery.fn.init(selector,context);
  };
  
  jQuery.fn=jQuery.prototype={
    init:function(selector,context){
      alert(selector);//弹出警告框
    }
  };
})();
window.οnlοad=function(){
  $("div.container");//得到“div.container”
  $("#result");//得到“#result”
  $("<div>hello,world</div>");//得到“<div>hello,world</div>”
  $(document.getElementById("result"));//得到“[object]”
}

 从这里我们可以得出,实际上$里面的参数(表达式字符串,ID字符串,HTML字符串,DOM对象),主要就是在init方法中各自实现它们自己的逻辑。

  现在列出init方法的具体实现:

init:function(selector,context){
    //Makesurethataselectionwasprovided
    selector=selector||document;
    //Handle$(DOMElement)
 if(selector.nodeType){
      this[0]=selector;
      this.length=1;
      this.context=selector;
      returnthis;
    }
    //HandleHTMLstrings
    if(typeofselector==="string"){
      //ArewedealingwithHTMLstringoranID?
      varmatch=quickExpr.exec(selector);
      //Verifyamatch,andthatnocontextwasspecifiedfor#id
      if(match&&(match[1]||!context)){
        //HANDLE:$(html)->$(array)
        if(match[1])
          selector=jQuery.clean([match[1]],context);
        //HANDLE:$("#id")
        else{
          varelem=document.getElementById(match[3]);
          //HandlethecasewhereIEandOperareturnitems
          //bynameinsteadofID
          if(elem&&elem.id!=match[3])
            returnjQuery().find(selector);
          //Otherwise,weinjecttheelementdirectlyintothejQueryobject
          varret=jQuery(elem||[]);
          ret.context=document;
          ret.selector=selector;
          returnret;
        }
      //HANDLE:$(expr,[context])
      //(whichisjustequivalentto:$(content).find(expr)
      }else
        returnjQuery(context).find(selector);
    //HANDLE:$(function)
    //Shortcutfordocumentready
    }elseif(jQuery.isFunction(selector))
      returnjQuery(document).ready(selector);
    //Makesurethatoldselectorstateispassedalong
    if(selector.selector&&selector.context){
      this.selector=selector.selector;
      this.context=selector.context;
    }
    returnthis.setArray(jQuery.makeArray(selector));
  }
 

 3. 现在分析 表达式,id,HTML字符串,DOM元素等等各自的实现:

 1)形如 $(document.getElementById("result")) 【jQuery(elements)】DOM元素的实现,通过init方法中的以下代码:

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

 selector.nodeType判断当selector为元素节点时,将length置为1,并且赋值于context,实际上context作为init的第二个参数,它意味着它的上下文节点就是selector该点,返回它的$(...)对象。

  2)形如 $("<div>hello,world</div>") 【jQuery(html,[ownerDocument])】HTML字符串的实现,通过init方法中的以下代码:

 

//判断selector为字符串
if(typeofselector==="string"){
  //quickExpr=/^[^<]*(<(.|s)+>)[^>]*$|^#([w-]+)$/
  //利用检查正则表达式HTML字符串还是元素ID字符串
  varmatch=quickExpr.exec(selector);
  
  if(match&&(match[1]||!context)){
    //处理HTML字符串
    if(match[1])
      selector=jQuery.clean([match[1]],context);
    //处理形如$("#id")
    else{
      //……
    }
  
  }
  //处理形如$("div.container")的表达式字符串
  else
    //……
}
//处理形如$(function),$(document).ready(function(){})的表示
elseif(jQuery.isFunction(selector)){
}
//……

 关键看到这样的一句代码,selector = jQuery.clean( [ match[1] ], context ); 继续查看clean都做了些什么:

clean:function(elems,context,fragment){
    context=context||document;
    //!context.createElementfailsinIEwithanerrorbutreturnstypeof'object'
    if(typeofcontext.createElement==="undefined")
      context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;
    //Ifasinglestringispassedinandit'sasingletag
    //justdoacreateElementandskiptherest
    if(!fragment&&elems.length===1&&typeofelems[0]==="string"){
      varmatch=/^<(w+)s*/?>$/.exec(elems[0]);
      if(match)
        return[context.createElement(match[1])];
    }
    varret=[],scripts=[],div=context.createElement("div");
    jQuery.each(elems,function(i,elem){
      if(typeofelem==="number")
        elem+='';
      if(!elem)
        return;
      //ConverthtmlstringintoDOMnodes
      if(typeofelem==="string"){
        //Fix"XHTML"-styletagsinallbrowsers
        elem=elem.replace(/(<(w+)[^>]*?)/>/g,function(all,front,tag){
          returntag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?
            all:
            front+"></"+tag+">";
        });
        //Trimwhitespace,otherwiseindexOfwon'tworkasexpected
        vartags=jQuery.trim(elem).toLowerCase();
        varwrap=
          //optionoroptgroup
          !tags.indexOf("<opt")&&
          [1,"<selectmultiple='multiple'>","</select>"]||
          !tags.indexOf("<leg")&&
          [1,"<fieldset>","</fieldset>"]||
          tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&
          [1,"<table>","</table>"]||
          !tags.indexOf("<tr")&&
          [2,"<table><tbody>","</tbody></table>"]||
          //<thead>matchedabove
          (!tags.indexOf("<td")||!tags.indexOf("<th"))&&
          [3,"<table><tbody><tr>","</tr></tbody></table>"]||
          !tags.indexOf("<col")&&
          [2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||
          //IEcan'tserialize<link>and<script>tagsnormally
          !jQuery.support.htmlSerialize&&
          [1,"div<div>","</div>"]||
          [0,"",""];
        //Gotohtmlandback,thenpeeloffextrawrappers
        div.innerHTML=wrap[1]+elem+wrap[2];
        //Movetotherightdepth
        while(wrap[0]--)
          div=div.lastChild;
        //RemoveIE'sautoinserted<tbody>fromtablefragments
        if(!jQuery.support.tbody){
          //Stringwasa<table>,*may*havespurious<tbody>
          vartbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?
            div.firstChild&&div.firstChild.childNodes:
            //Stringwasabare<thead>or<tfoot>
            wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?
              div.childNodes:
              [];
          for(varj=tbody.length-1;j>=0;--j)
            if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)
              tbody[j].parentNode.removeChild(tbody[j]);
          }
        //IEcompletelykillsleadingwhitespacewheninnerHTMLisused
        if(!jQuery.support.leadingWhitespace&&/^s/.test(elem))
          div.insertBefore(context.createTextNode(elem.match(/^s*/)[0]),div.firstChild);
        
        elem=jQuery.makeArray(div.childNodes);
      }
      if(elem.nodeType)
        ret.push(elem);
      else
        ret=jQuery.merge(ret,elem);
    });
    if(fragment){
      for(vari=0;ret[i];i++){
        if(jQuery.nodeName(ret[i],"script")&&(!ret[i].type||ret[i].type.toLowerCase()==="text/javascript")){
          scripts.push(ret[i].parentNode?ret[i].parentNode.removeChild(ret[i]):ret[i]);
        }else{
          if(ret[i].nodeType===1)
            ret.splice.apply(ret,[i+1,0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))));
          fragment.appendChild(ret[i]);
        }
      }
      
      returnscripts;
    }
    returnret;
  }
 

 这么长的一串代码 实际上最后访问的是一个ret为变量的数组,而数组中的元素变为以DOM元素的对象,而它的innerHTML正好就是刚才的HTML字符串。

 3)形如 $("#result") 【jQuery(expression,[context])】ID字符串的实现,通过init方法中的以下代码:

//处理形如$("#result")
else{
  //match[3]得到ID的值如:result
  varelem=document.getElementById(match[3]);
  if(elem&&elem.id!=match[3])
    returnjQuery().find(selector);
 //调用jQuery(elements)方式
  varret=jQuery(elem||[]);
 //默认上下文DOM为window.document
  ret.context=document;
  ret.selector=selector;
  returnret;
}

 根据match[3]可以得到DOM对象elem,并且调用2)介绍的jQuery(elements),最后返回一个ret为变量的jquery对象。

  4)形如 $("div .container") 【jQuery(expression,[context])】表达式字符串的实现,通过init方法中的以下代码:

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

关键看到这样的一句代码,jQuery().find( selector ); 继续查看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);
  }
}

 先看下表达式不包含“,”符号的时候,调用pushStack方法,方法为:

/将一系列元素推入栈中
pushStack:function(elems,name,selector){
  varret=jQuery(elems);
 //将上个对象的引用推入栈中
  ret.prevObject=this;
  ret.context=this.context;
 //关键字为find时,在原有selector的基础上,继续增加selector
 //如$("div").find("p")意思就是$("divp")
  if(name==="find")
    ret.selector=this.selector+(this.selector?"":"")+selector;
  elseif(name)
    ret.selector=this.selector+"."+name+"("+selector+")";
 //返回最新的Jquery对象
  returnret;
}

 

 注意这里看到 ret.prevObject= this; 这个方法在$(...).andSelf()和$(...).end()中调用,对于筛选或查找后的元素,返回前一次元素状态它是很有用的。

  接着调用 jQuery.find( selector, this[0], ret ); ,首先我们看到有这样的几句代码:

jQuery.find=Sizzle;
jQuery.filter=Sizzle.filter;
jQuery.expr=Sizzle.selectors;
jQuery.expr[":"]=jQuery.expr.filters;
//……
window.Sizzle=Sizzle;

 jQuery.find方法转去调用全局的Sizzle对象了(实际上这里运用到了Javascript设计模式中的适配器模式,jquery.find 实际上调用的是Sizzle的对象。关于Javascript适配器模式,我将接下来的Javascript乱弹设计模式系列文章中具体叙 述),Sizzle对象定义为:

varSizzle=function(selector,context,results,seed){
  results=results||[];
  context=context||document;
  if(context.nodeType!==1&&context.nodeType!==9)
    return[];
  
  if(!selector||typeofselector!=="string"){
    returnresults;
  }
 varparts=[],m,set,checkSet,check,mode,extra,prune=true;
  
  //Resetthepositionofthechunkerregexp(startfromhead)
  chunker.lastIndex=0;
  
 while((m=chunker.exec(selector))!==null){
    parts.push(m[1]);
    
    if(m[2]){
 extra=RegExp.rightContext;
      break;
    }
  }
  if(parts.length>1&&origPOS.exec(selector)){
    if(parts.length===2&&Expr.relative[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{
    varret=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){
      varcur=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"Syntaxerror,unrecognizedexpression:"+(cur||selector);
  }
  if(toString.call(checkSet)==="[objectArray]"){
    if(!prune){
      results.push.apply(results,checkSet);
    }elseif(context.nodeType===1){
      for(vari=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(vari=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);
  }
  returnresults;
};
 

 呵呵,好长的一段代码,实际最关键的一句代码:Sizzle.find(parts.pop(),parts.length===1&&context.parentNode?context.parentNode:context,isXML(context));

 对表达式字符串进行解析,最后返回一个jQuery对象,它就是 表达式字符串 最后想要的jQuery对象。

 当表达式包含“,”符号的时候,查看这样的一句代码:return this.pushStack( /[^+>] [^+>]/.test( selector ) ? jQuery.unique( elems ) : elems, "find", selector );

 它的意思是当表达式字符串包含“+”,“>”作为选择器(比如$("form > input") 或者 $("label + input") )的时候,将作为jQuery.map返回值的elems数组删除重复的元素,这个是有必要的。

  最后也是返回一个jQuery对象,它就是 表达式字符串 最后想要的jQuery对象。

  5)最后一点,形如 $(function) , $(document).ready(function(){})的表示,通过init方法中的以下代码来实现:

//处理形如$(function),$(document).ready(function(){})的表示
elseif(jQuery.isFunction(selector))
  returnjQuery(document).ready(selector);

 如果判断selector是函数的话,将执行jQuery(document).ready(selector);

  ready方法具体为:

ready:function(fn){
  //绑定事件监听
  bindReady();
  if(jQuery.isReady)
    fn.call(document,jQuery);
  else
    jQuery.readyList.push(fn);
  returnthis;
}
 

 bindReady方法将事件绑定在文档加载完毕之后,最后通过调用fn.call( document, jQuery );来激发事件的执行。

  好了,jQuery的核心函数的原理机制就是这样的,下一篇我将谈下jQuery对象访问和数据缓存的原理机制。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值