前言
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.onload=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对象访问和数据缓存的原理机制。