
在前面的某篇文章中,我对jQuery.fn中的init方法做了一些阐述,整体的思路相对也比较完整,但是总感觉有些不尽意。因为init方法是jQuery源码中一个非常重要的方法,几乎所有的dom创建、dom选择等功能都是以它的实现为基础。所以本篇文章中,我们就来仔仔细细的探讨一下jQuery.fn.init.
首先,我参照的jquery版本为v2.0.3,请您对照此版本来进行下面的阅读。由于这段代码比较长,所以这段代码我就不罗列在这里了。我将会按照具体的逻辑来逐条详解这段代码。
一、回顾jQuery的使用
在平常jquery的操作中,我们是通过$(selector)这样的方式的方式使用jQuery,通过传入selector值的不同,我们主要有下面的几种使用方式
// 传入不合法的参数
1、$(""), $(null), $(undefined), $(false)
// 获取元素节点
2、$('#div1') $('.box') $('div') $('#div1 div.box')
//创建元素节点
3、$('<li>') $('<li>1</li><li>2</li>')
// 选取dom
4、$(this) $(document)
// 传入参数为函数的情况
5、$(function(){})
// 传入其他的一些不常见的参数
6、$([]) $({})
也许您对上面的六种用法不完全了解,这很正常,因为有些方式确实在我们平常的工作中用的比较少。下面,我们就来一一讲解。
二、源码逐行分解
1、jQuery中的this对象
jQuery中的this对象和原生js中的this对象有所不同,jQuery中的this是一个类数组对象,具体的结构如下图所示:

生成图片上的源代码代码如下所示:对比图和代码,我们就可以发现jQuery中的this除了选择到元素外,还添加了length、context、selector等属性。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
console.log($('li'))
</script>
至于这段代码的使用,我们需要看下面的css方法的代码:code1中代码执行的逻辑就是code2中的代码,这也就印证了this这个特殊对象的使用。
code1
$('li').css('backgroundColor','red')
code2
for(var i = 0; i < this.length; i++){
this[i].style.background = 'red'
}
2、init方法传入方法的解释说明
selector | context | rootjQuery |
---|---|---|
jquery | 上下文对象 | $(document) |
3、当传入不合法的selector时,直接返回this对象
不合法的this对象包括空字符串、null、undefined、false。源码如下:
if ( !selector ) {
return this;
}
4、传入的参数selector为字符串
这种情况是init方法中最复杂的部分,也是我们经常使用到的。主要处理的情况有上面六种中的2、3两种情况,既:
// 获取元素节点
2、$('#div1') $('.box') $('div') $('#div1 div.box')
//创建元素节点
3、$('<li>') $('<li>1</li><li>2</li>')
4.1 处理元素的创建
下面的判断条件就是处理创建标签的情况。源码随后,最后我们得到了match = [ null, selector, null ];这个结果。
$('<li>')
$('<li>1</li><li>2</li>')
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
}
match的值
selector | match |
---|---|
match = [ null, '<li>', null ] | $('<li>') |
match = [ null, '<li>1</li><li>2</li>', null ] | $('<li>1</li><li>2</li>') |
4.2 处理获取元素节点
这部分就是处理下面的这种情况,也就是选择元素节点。通过这段代码之后,我们有会得出一组不同情况下的match值。
2、$('#div1') $('.box') $('div') $('#div1 div.box')
else {
match = rquickExpr.exec( selector );
}
match的值
selector | match |
---|---|
match = null |
|
match = [ '#div1', null, 'div1' ] | $('#div1') |
match = [ '<li>hello', '<li>', null ] | $('<li>hello') |
5、处理创建标签和id选择
经过上面的分析,我们已经能拿到match的相关值了。下面我们就通过match值的不同来继续梳理我们的逻辑。下面的判断条件就是处理创建标签和id选择的条件了。
if ( match && (match[1] || !context) ) {
...
}
5.1、创建元素节点
根据上面分析的match结果,match[1]存在的情况为下面三种情况。
1、$('<li>')
2、$('<li>1</li><li>2</li>')
3、$('<li>hello')
对照着上面的三种使用情况,我么来分析下面的这段源码。
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context;
// scripts is true for back-compat
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 ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
his[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
}
5.1.1 获取执行上下文
context = context instanceof jQuery ? context[0] : context;
这里对象执行上下文对象进行了修正,如果传入的context为jQuery对象(如$(document)),我们就需要使用context[0]的方式选择原生js的对象(如document)。
5.1.2 jQuery.parseHTML解析元素字符串
jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document, true
)
jQuery.parseHTML这个方法既可以在jQuery内部使用,也可以提供给外部使用。
在外部使用:
通过使用parseHTML方法可以将str转化为数组。
var str = '<li>1</li><li>2</li><li>3</li>'
var arr = jQuery.parseHTML(str)
console.log(arr) //[li, li, li]
在内部使用:
在内部使用的时候,我们给其多传递了两个参数。多传递的第二个参数是执行上下文对象,第三个参数的是一个boolean类型的值,其值默认为false,表示不会执行str中的javascript代码,可以将其设置为true,表示可以执行JavaScript代码。
var str = '<li>1</li><li>2</li><li>3</li><script>alert("1")<\/script>'
var arr = jQuery.parseHTML(str, document, true)
$.each(arr, function(i){
$('ul').append( arr[i] )
})
关于parseHTML具体的实现细节,我们在后续的文章中再介绍,这里我们只需要知道,我们的目的是将标签字符串str格式化为一个数组。
5.1.3 jQuery.merge合并数组
我们再来看一下这个合并数据和jQuery对象的方法吧。
合并数组
var arr = ['a', 'b']
var arr1 = ['c', 'b']
console.log($.merge(arr, arr1)) // ["a", "b", "c", "b"]
合并jQuery对象
除了上面的合并数组外,我们还可以合并jQuery对象,如下:
var arr = {
0: 'a',
1: 'b',
length: 2
}
var arr1 = ['c', 'b']
console.log($.merge(arr, arr1))
上面的代码运行的结果如下图。同样,jQuery.merge方法具体的细节,我们在后面的文章中会详细介绍。

5.1.4 对单标签的情况单独处理
单标签的情况如下:
$('<li>', {title: 'hi', html: 'abcd'}).appendTo('ul')
$('<li></li>', {title: 'hi', html: 'abcd'}).appendTo('ul')
注意,下面的方式是不行的。
$('<li></li><li></li>', {title: 'hi', html: 'abcd'}).appendTo(' ul ')
那现在,我们就带着上面的分析,接着来看这段处理单标签的源码吧。
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
假设这段源码中context为{title: 'hi', html: 'abcd'},我们对这个对象进行遍历,如果这个对象中的键为jQuery中的函数时if ( jQuery.isFunction( this[ match ] ) ) ,我们执行这个方法,并且为其赋值 this[ match ]( context[ match ] )。当然,如果是属性的话,我们就给他添加属性 this.attr( match, context[ match ] )。
5.2、根据id获取元素
else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
通过id选择元素的方法就显得比较简单了,包括获取元素,拼装this对象然后返回。
6、当不传入执行上下文context或者传递进去的是jQuery对象
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
} else {
return this.constructor( context ).find( selector );
}
这段代码的处理结果的逻辑如下。你可能对 context.jquery 比较奇怪,这样写为了判断context为$(document)时的情况,因为只有jQuery对象才会有jquery这个属性。
$('ul', document).find('li'); else jQuery(document).find()
$('ul', $(document)).find('li') if: jQuery(document).find()
7、当传入的参数为DOM节点
else if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
}
这里的node比如:document,window以及原生的dom节点等。例如下面这段代码:
<ul id="list"></ul>
<script>
var list = document.getElementById('list')
console.log($(list))
</script>
获取到的结果如下图所示:

8、当传入的参数为函数
else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
这种情况,我们应该很常见。比如,如果这个都不熟悉,那就得好好地反思一下了。
$(document).ready(function(){})
9、当传入的参数为jQuery对象
根据只有jQuery对象才有selector属性的原则,我们可以通过这段代码处理下面的方法。
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
$($('#list')) ==>> $('#list')
10、合并this对象
return jQuery.makeArray( selector, this );
这个方法就比较简单了,就是将selector属性和之前的this对象合并在一起,当然这也要借助jQuery.makeArray()这个方法来实现。我们来简单看一下makeArray这个方法的用法。
<ul id="list"></ul>
<script>
var list = document.getElementById('list')
console.log( $.makeArray(list, {length : 0 }))
</script>
运行的结果如图所示:

至此,我们的文章结束了,当然里边还有没有写到点,比如正则表达式,关于jQuery中的正则表达式我会在后面的文章中单独去讲解。感谢您的阅读,祝您身体健康,阖家幸福!
网友评论