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中的选择器。感兴趣的朋友们请继续关注毛票票,谢谢大家的支持。