在jQuery源码阅读(二)里面,大体介绍了jQuery创建对象时如何创建的,并且对init函数里面的函数进行了梳理,但具体的逻辑还是没有彻底搞清楚,这一篇博客主要是对init函数的一个详细梳理。
在理解init函数内部逻辑之前,我们先想想用$()和jQuery()来创建jQuery对象时,参数有哪些可能的情况?或者想想我们平时会用到哪些?这在jQuery源码阅读(二)里面也有说到,我们再来回顾一下:
$(),$('') //这种情况基本很少用到,但还是需要考虑
返回结果都是一个空对象:
$('<li></li>');
$('<img />');
$('<ul><li>苹果</li></ul>');
返回结果如下:
可以看到每个返回结果都是类似与数组的对象,为什么说类似于数组?因为他们也是按照0,1,…下标来保存元素的,并且还有length属性。
对于最后一个$('<ul><li>苹果</li></ul>');
比较复杂的情况,返回的同样是一个长度为1的类数组(对象),它里面的元素是ul,那么li元素跑到哪里去了?将ul元素打开,可以找到在childNode下面有li元素。
那么这个在jQuery里面是如何实现的?我们后面理源码的时候再分析。
<body>
<input id="btn" type="button" value="点击按钮" />
<input type="text" value="输入" />
<input type="text" id="128738-" />
<div class='.box'>一个小盒子</div>
<ul>
<li>
<a href="#" class="first">链接列表一</a>
</li>
<li>
<a href="#">链接列表二</a>
</li>
</ul>
</body>
$('#128738-');
$('.box')
$('ul li .first')
$(function(){
//当页面处于ready状态时,触发函数;
//即当页面元素、需要的外部样式表以及脚本加载完成时触发,而不必等到需要的所有图片加载出来。
})
还有一种情况:
$(document.getElementById('128738-'));
//这里需要说明一下,CSS选择器命名规则是不允许数字开头和纯数字形式的,但是在原生JS或者jQuery获取选择器时,是可以选到对应选择器所标注的元素的。
上述方法可以看作是将原生DOM节点转换成jQuery对象。
根据jQuery源码阅读(二)所讲,除了上面常用的一些用法,有效参数还包括jQuery对象和Object对象等情况,但是这两种并不常用。
经过上面所有参数情况的分析,这才理解了之前看的下面这部分源码:
//之前始终不明白,为什么jQuery对象要有length对象?为什么不管init函数里面走哪个分支,最后都对this.selector和this.length赋值了
// Start with an empty selector
selector: "",
// The current version of jQuery being used
jquery: "1.7.2",
// The default length of a jQuery object is 0
length: 0,
现在明白了,就是jQuery就想利用数组的一些属性方法来便于操作,但init这个是创建对象的方法,也就是说返回的肯定是个对象,因此为了能用数组的属性和方法,将其改造成类似于数组的对象就好了。
下来我先搭一个init()函数的源码框架,便于大家看清楚:
init: function(selector, context, rootjQuery){
//情况一: 输入参数为null,undefined或者""
if(! selector)
{
return this;
}
//输入参数为DOM元素(通过nodeType来判断是否为DDOM元素)
if( selector.nodeType)
{
this.context = this[0] = selector;
this.length = 1;
return this;
}
var match;
if( typeof selector === 'string')
{
//如果是标签
//$('<div></div>'); $('<div>dekwpoioewueoi</div>'); $('<ul><li>列表复杂标签</li></ul>')
if( selector.charAt(0) === '<' && selector.charAt(selector.length - 1) === '>' && selector.length >= 3) {
match = [null, selector, null];
}
else //不是完整标签;
//$('ul'); $('#id') $('.class'); $('ul li.class'); $('<div>jdioeusroiuewo')
{
match = quickExpr.exec(selector);
//如果是标签 $('<div>jdioeusroiuewo'), match = ['<div>jdioeusroiuewo', 'div', undefined];
//如果是id选择器$('#id') , match = ['#id', undefined, 'id'];
//如果是其他情况,match = null;
}
//上面得到了match数组,不同情况对应不同的值;下来根据不同结果再分
if(match && (match[1] || !context)) //所有match为真的情况
{
//是标签
//$('<div></div>'); $('<div>dekwpoioewueoi</div>'); $('<ul><li>列表复杂标签</li></ul>')
//$('<div>jdioeusroiuewo') match = ['<div>jdioeusroiuewo', 'div', undefined];
//id选择器$('#id') match = ['#id', undefined, 'id'];
if(match[1]) //标签、不完整标签、以及复杂标签
{
//jQuery 对象中context的概念,表示当前上下文,一般情况下都是document,如果有iFrame的话可能是iFrame里面的contentWindow.document
//所以context还是有必要的
context = context instanceof jQuery ? context[0] : context;
} else { //id选择器
}
} else { //这几种情况$('ul'); $('.class'); $('ul li.class');
}
}
//输入参数为函数,只有一种用法:$(function(){})相当于$.ready(function(){})
if( jQuery.isFunction(selector))
{
//调jQuery.ready()
}
if(selector.selector !== undefined)
{
//返回jQuery对象的Copy
this.selector = selector.selector;
this.context = selector.context;
return this;
}
//其他情况
return jQuery.makeArray(this, selector);
}
上面这个框架的逻辑与jQuery源码阅读(二)里面讲的是一致的,这里将参数selector为字符串的情况再细分了一下,下来我们主要分析selector为字符串的情况。
selector为字符串主要有这么几种情况:
$('#id')
$('.class')
$('li') $('ul li .first')
$('<li></li>')
$('<li>苹果</li>')
$('<ul><li></li><ul>')
$('<img />')
$('<img src="#" />')
大致可以分为上面几类。
根据上面的源码框架,将其分为一下两大类别:
第一种:标签
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
match = [ null, selector, null ];
//后面再说match的作用
}
$('<li></li>') //match = [null, '<li></li>', null]
$('<li>苹果</li>') //match = [null, '<li>苹果</li>', null]
$('<ul><li></li><ul>') //match = [null, '<ul><li></li><ul>', null]
$('<img />') //match = [null, '<img />', null]
$('<img src="#" />') //match = [null, '<img src="#" />', null]
第二种:非标签或非完整标签
{
match = quickExpr.exec(selector);
}
这里注意下面各个情况得到的match,关于正则表达式的匹配我也在上一篇博客 jQuery源码阅读之正则表达式作了说明
$('#id') //match = ['#id', undefined, 'id']
$('<li>abcdefg') //match = ['<li>abcdefg', '<li>', undefined]
$('.class') //match = null
$('li') $('ul li .first') ////match = null
下来就是根据match的值来分情况处理:
if(match && (match[1] || !context)){
//match[1]为真是包含标签的情况,不管是完整标签,还是不完整标签,或者是复杂标签
!context为真是selector为ID的情况,因为ID 选择是上下文的
if(match[1]) { //selector为标签的情况
context = context instanceof jQuery ? context[0] : context;
ret = rsingleTag.exec(selector); //匹配单标签
//包括$('<li></li>'),$('<li>'), 也包括$('<img />')
if(ret)
{
if(jQuery.isPlainObject(context))
{
//为什么会有这种情况呢?因为在jQuery中允许这种用法:$('<li></li>', { title: 'Hello', html: 'Hellen'}),后面加测试来验证
selector = [ document.createElement( ret[1] ) ];
//去给DOM元素添加属性
jQuery.fn.attr.call( selector, context, true );
}
else
{
//如果只是一个简单的标签,直接掉原生的createElement就可以了
selector = [ doc.createElement( ret[1] ) ];
}
} else //ret为空,$('<li>apple</li>'); $('<img src="#" />'); $('<ul><li></li></ul>')这几种类似情况
{
//带内容的标签,复杂标签的情况,调jQuery.buildFragment方法,这个后面再看,看了会再更新回来
ret = jQuery.buildFragment( [ match[1] ], [ doc ] )( [ match[1] ], [ doc ] );
selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
}
return jQuery.merge( this, selector );
}
else //selector为ID的情况
{
//调原生JS getElementById方法
elem = document.getElementById(match[2]);
//判断元素找到?
if ( elem && elem.parentNode ) {
//(需考虑浏览器兼容问题,在IE和Opera浏览器中,调document.geElementById("id")会选中name属性为id的元素)
if ( elem.id !== match[2] ) {
//调Sizzle模块中的find方法
return rootjQuery.find( selector );
}
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
} else if( !context || context.jquery ){ //如果为其他选择器或复杂选择器
//无上下文时,调$(document).find()
//若有上下文,判断上下文是否为jQuery对象,如果是,则调context.find()方法
return ( context || rootjQuery ).find( selector );
} else //如果context不为jQuery对象,则先将其转换成jQuery对象再调find()方法。
{
return this.constructor( context ).find( selector );
}
}
到此,selector为字符串的情况就分析结束了,可以看到这整个就是Init()函数的大头,并且也是我们平时常用的情况。所以还是有必要缕清楚的。
上面缕的过程中,主要涉及到两个jQuery复杂点的静态函数jQuery.find()和jQuery.buildFragment()方法,这里由于篇幅已经比较长了,所以决定后面再理。相信看到这里的已经很不容易了,希望大家一起努力,一起进步!
PS: 关于jQuery.buildFragment函数的分析点击这里