前言
Jquery(http://jquery.com/)是一个轻量级,快速简洁的Javascript框架,它的容量小巧,简洁和简短的语法, 容易记;用户能更方便地处理HTML DOM、Events、实现动画效果,并且提供Ajax的支持。目前最新版本为jQuery 1.3.1(http://jqueryjs.googlecode.com/files/jquery-1.3.1.js),这系列文章将对该版本的源码进行阐述。
现在开始本系列的第一篇,Jquery核心函数,内容主要包括:
分析
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对象访问和数据缓存的原理机制。