jQuery源码分析之init的详细介绍
init构造器
由于这个函数直接和jQuery()的参数有关,先来说下能接受什么样的参数。
源码中接受3个参数:
init:function(selector,context,root){ ... }
- jQuery(),空参数,这个会直接返回一个空的jQuery对象,returnthis。
- jQuery(selector[,context]),这是一个标准且常用法,selector表示一个css选择器,这个选择器通常是一个字符串,#id或者.class等,context表示选择范围,即限定作用,可为DOM,jQuery对象。
- jQuery(element|elements),用于将一个DOM对象或DOM数组封装成jQuery对象。
- jQuery(jQueryobject|object),会把普通的对象或jQuery对象包装在jQuery对象中。
- jQuery(html[,ownerDocument]),这个方法用于将html字符串先转成DOM对象后在生成jQuery对象。
- jQuery(html,attributes),和上一个方法一样,不过会将attributes中的方法和属性绑定到生成的htmlDOM中,比如class等。
- jQuery(callback),此方法接受一个回掉函数,相当于window.onload方法,只是相对于。
介绍完入口,就开始来看源码。
init:function(selector,context,root){ varmatch,elem; //处理:$(""),$(null),$(undefined),$(false) if(!selector){ returnthis; } //rootjQuery=jQuery(document); root=root||rootjQuery; //处理HTML字符串情况,包括$("<div>")、$("#id")、$(".class") if(typeofselector==="string"){ //此部分拆分,留在后面讲 //HANDLE:$(DOMElement) }elseif(selector.nodeType){ this[0]=selector; this.length=1; returnthis; //HANDLE:$(function) }elseif(jQuery.isFunction(selector)){ returnroot.ready!==undefined?root.ready(selector): //Executeimmediatelyifreadyisnotpresent selector(jQuery); } returnjQuery.makeArray(selector,this); }
上面有几点需要注意,root=root||rootjQuery;,这个参数在前面介绍用法的时候,就没有提及,这个表示document,默认的话是rootjQuery,而rootjQuery=jQuery(document)。
可以看出,对于处理$(DOMElement),直接是把jQuery当作一个数组,this[0]=DOMElement。其实,这要从jQuery的基本构造讲起,我们完成一个$('div.span')之后,然后一个jQuery对象(this),其中会得到一组(一个)DOM对象,jQuery会把这组DOM对象当作数组元素添加过来,并给一个length。后面就像一些链式函数操作的时候,若只能对一个DOM操作,比如width、height,就只对第一个元素操作,若可以对多个DOM操作,则会对所有DOM进行操作,比如css()。
jQuery大题思路如下,这是一个非常简单点实现:
jQuery.prototype={ //简单点,假设此时selector用querySelectorAll init:function(selector){ varele=document.querySelectorAll(selector); //把this当作数组,每一项都是DOM对象 for(vari=0;i<ele.length;i++){ this[i]=ele[i]; } this.length=ele.length; returnthis; }, //css若只有一个对象,则取其第一个DOM对象 //若css有两个参数,则对每一个DOM对象都设置css css:function(attr,val){ for(vari=0;i<this.length;i++){ if(val==undefined){ if(typeofattr==='object'){ for(varkeyinattr){ this.css(key,attr[key]); } }elseif(typeofattr==='string'){ returngetComputedStyle(this[i])[attr]; } }else{ this[i].style[attr]=val; } } }, }
所以对于DOMElement的处理,直接将DOM赋值给数组后,returnthis。
jQuery.makeArray是一个绑定数组的函数,和上面的原理一样,后面会谈到。
在介绍下面的内容之前,先来介绍一个jQuery中一个识别Html字符串的正则表达式,
varrquickExpr=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/; rquickExpr.exec('<div>')//["<div>","<div>",undefined] rquickExpr.exec('<div></div>')//["<div></div>","<div></div>",undefined] rquickExpr.exec('#id')//["#id",undefined,"id"] rquickExpr.exec('.class')//null
上面这一系列的正则表达式exec,只是为了说明rquickExpr这个正则表达式执行后的结果,首先,如果匹配到,结果数组的长度是3,如果匹配到<div>这种html,数组的第三个元素是underfined,如果匹配到#id,数组的第二个元素是underfined,如果匹配不到,则为null。
另外还有一个正则表达式:
varrsingleTag=(/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i); rsingleTag.test('<div></div>')//true rsingleTag.test('<div></div>')//true rsingleTag.test('<divclass="cl"></div>')//false rsingleTag.test('<div></ddiv>')//false
这个正则表达式主要是对html的字符串进行验证,达到不出差错的效果。在这里不多介绍exec和正则表达式了。
下面来看下重点的处理HTMl字符串的情况:
if(selector[0]==="<"&&selector[selector.length-1]===">"&&selector.length>=3){ //这个其实是强行构造了匹配html的情况的数组 match=[null,selector,null]; }else{ match=rquickExpr.exec(selector); } //macth[1]限定了html,!context对#id处理 if(match&&(match[1]||!context)){ //HANDLE:$(html)->$(array) if(match[1]){ //排除context是jQuery对象情况 context=contextinstanceofjQuery?context[0]:context; //jQuery.merge是专门针对jQuery合并数组的方法 //jQuery.parseHTML是针对html字符串转换成DOM对象 jQuery.merge(this,jQuery.parseHTML( match[1],context&&context.nodeType?context.ownerDocument||context:document,true)); //HANDLE:$(html,props) if(rsingleTag.test(match[1])&&jQuery.isPlainObject(context)){ for(matchincontext){ //此时的match非彼时的match if(jQuery.isFunction(this[match])){ this[match](context[match]); //...andotherwisesetasattributes }else{ this.attr(match,context[match]); } } } returnthis; //处理match(1)为underfined但!context的情况 }else{ elem=document.getElementById(match[2]); if(elem){ //this[0]返回一个标准的jQuery对象 this[0]=elem; this.length=1; } returnthis; } //处理一般的情况,find实际上上Sizzle,jQuery已经将其包括进来,下章详细介绍 //jQuery.find()为jQuery的选择器,性能良好 }elseif(!context||context.jquery){ return(context||root).find(selector); //处理!context情况 }else{ //这里constructor其实是指向jQuery的 returnthis.constructor(context).find(selector); }
关于nodeType,这是DOM的一个属性,详情Node.nodeTypeMDN。nodeType的值一般是一个数字,比如1表示DOM,3表示文字等,也可以用这个值是否存在来判断DOM元素,比如context.nodeType。
整个init函数等构造逻辑,非常清晰,比如(selector,context,root)三个参数,分别表示选择的内容,可能存在的的限制对象或Object,而root则默认的jQuery(document)。依旧采用jQuery常用的方式,对每一个变量的处理都非常的谨慎。
如果仔细看上面两部分源代码,我自己也加了注释,应该可以把整个过程给弄懂。
find函数实际上是Sizzle,已经单独出来一个项目,被在jQuery中直接使用,将在下章介绍jQuery中的Sizzle选择器。通过源码,可以发现:
jQuery.find=functionSizzle(){...} jQuery.fn.find=function(selector){ ... //引用jQuery.find jQuery.find() ... }
衍生的函数
init函数仍然调用了不少jQuery或jQuery.fn的函数,下面来逐个分析。
jQuery.merge
这个函数通过名字,就知道它是用来干什么的,合并。
jQuery.merge=function(first,second){ varlen=+second.length, j=0, i=first.length; for(;j<len;j++){ first[i++]=second[j]; } first.length=i; returnfirst; }
这样子就可以对类似于数组且有length参数的类型进行合并,我感觉主要还是为了方便对jQuery对象的合并,因为jQuery对象就是有length的。
jQuery.parseHTML
这个函数也非常有意思,就是将一串HTML字符串转成DOM对象。
首先函数接受三个参数,第一个参数data即为html字符串,第二个参数是document对象,但要考虑到浏览器的兼容性,第三个参数keepScripts是为了删除节点里所有的scripttags,但在parseHTML里面没有体现,主要还是给buildFragment当作参数。
总之返回的对象,是一个DOM数组或空数组。
jQuery.parseHTML=function(data,context,keepScripts){ if(typeofdata!=="string"){ return[]; } //平移参数 if(typeofcontext==="boolean"){ keepScripts=context; context=false; } varbase,parsed,scripts; if(!context){ //下面这段话的意思就是在context缺失的情况下,建立一个document对象 if(support.createHTMLDocument){ context=document.implementation.createHTMLDocument(""); base=context.createElement("base"); base.href=document.location.href; context.head.appendChild(base); }else{ context=document; } } //用来解析parsed,比如对"<div></div>"的处理结果parsed:["<div></div>","div"] //parsed[1]="div" parsed=rsingleTag.exec(data); scripts=!keepScripts&&[]; //Singletag if(parsed){ return[context.createElement(parsed[1])]; } //见下方解释 parsed=buildFragment([data],context,scripts); if(scripts&&scripts.length){ jQuery(scripts).remove(); } returnjQuery.merge([],parsed.childNodes); }
buildFragment函数主要是用来建立一个包含子节点的fragment对象,用于频发操作的添加删除节点。parsed=buildFragment([data],context,scripts);建立好一个fragment对象,用parsed.childNodes来获取这些data对应的HTML。
jQueyr.makeArray
jQuery里面的函数调用,真的是一层接一层,虽然有时候光靠函数名,就能知道这函数的作用,但其中思考之逻辑还是挺参考意义的。
jQuery.makeArray=function(arr,results){ varret=results||[]; if(arr!=null){ if(isArrayLike(Object(arr))){ jQuery.merge(ret,typeofarr==="string"?[arr]:arr); }else{ push.call(ret,arr); } } returnret; }
makeArray把左边的数组或字符串并入到右边的数组或一个新数组,其中又间接的引用jQuery.merge函数。
接下来是着isArrayLike函数,可能需要考虑多方面的因素,比如兼容浏览器等,就有了下面这一长串:
functionisArrayLike(obj){ //Support:realiOS8.2only(notreproducibleinsimulator) //`in`checkusedtopreventJITerror(gh-2145) //hasOwnisn'tusedhereduetofalsenegatives //regardingNodelistlengthinIE varlength=!!obj&&"length"inobj&&obj.length, type=jQuery.type(obj); if(type==="function"||jQuery.isWindow(obj)){ returnfalse; } returntype==="array"||length===0||typeoflength==="number"&&length>0&&(length-1)inobj; }
总结
这篇文章主要介绍了jQuery中比较重要的入口函数,之后将会继续讲解Sizzle,jQuery中的选择器。感兴趣的朋友们请继续关注毛票票,谢谢大家的支持。