jQuery源码分析3--jQ原型1
jQuery1.8.1的95~289行,关于jQuery的prototype上构造函数init方法的介绍。
一、prototype的赋值与覆盖
首先是给jQuery的原型重新覆盖了对象:
jQuery.fn = jQuery.prototype = {//.......}
而在覆盖的对象中有个constructor属性。需要这样赋值的原因是此处我们是直接覆盖了原型对象,而不是修改他的属性,constructor会在创建时,指向jQuery,因为被覆盖了,所以需要重新指向。比如:
var Fun = function() { // Fun构造函数
this.place = 'earth';
};
Fun.prototype.show = function() { // 添加属性,属于赋值操作
console.log(this.place);
};
var fn = new Fun();
console.log(fn.constructor === Fun); // true
Fun.prototype = { // 覆盖了原先的prototype
show: function() {
console.log(this.place);
}
};
var fn1 = new Fun();
console.log(fn1.constructor === Fun); // false,因为是{}覆盖的,所以指向Object
正是这种原因,这儿对constructor进行了重新的赋值。
二、init函数
该init函数是我们每次使用$(jQuery)时,创建jQuery对象的构造函数。
init: function( selector, context, rootjQuery ) {
init函数带有三个参数,第一个是选择器,可以是id、class或复杂的字符串,也可以是dom对象等。第二个参数是上下文,正常情况下都是document,有些时候,当我们在iframe中使用时,需要对这个context进行赋值。第三个参数是document的jQ对象,在906行:rootjQuery = jQuery(document)。
空值过滤
进入函数内部,首先是过滤了空字符串("")、null、undefined、false这些值,直接返回this。
dom转化jqdom
紧接着的if用于处理dom,我们都知道,$(dom)这样使用时,我们可以得到这个dom的jQ对象。于是,这儿给new出的实例添加了个0的属性,且值是该dom。并将length置为1。
注:这儿的对象是类数组,他的属性值类似于数组的下标,且含有length,但他实质上不是数组,就和函数内部的arguments类似。
过滤了以上的条件后,开始处理字符串。我们想想,平时,我们可以写$('#box')、$('.class')、$('#box div')、$('
')、$('处理字符串
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
该方式查找前后都是标签的html字符串,像$('
')、$('match = [ null, selector, null ];
而else中,利用正则的匹配也得到了一个长度是三的类似的数组,
match = rquickExpr.exec( selector );
rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
59行,用来检测html的字符串形式的。正则匹配用到的知识就不展开了
正则匹配完成之后,会对匹配结果进行判断,
if ( match && (match[1] || !context) ) {
当存在match时,且match中第二个元素是true或是false但context上下文不存在时才会走。所以进入该if的有:$('
')、$('创建元素
随后使用 if ( match[1] ) { 来区分掉刚才上下文为false的id查找字符串。因此,能够进入if的都是创建元素的字符串。接下来,首先是对context的赋值,保证该context是原生的dom对象,并且对doc变量进行赋值。随后调用jQ的工具方法parseHTML将匹配到的HTML字符串转化为jq对象。如何转化的,到parseHTML定义的地方在叙述。
注:$('
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
this.attr.call( selector, context, true );
}
检测当时单个元素时,且context是一个纯对象时,就系那个context下的属性给到创建的那个元素上。这也就是当我们写$('
', {'title': 'aaa', html: 'abcd'}).appendTo('html')这种方式时,会给新创建的p元素添加两个属性。再来看下进入else中的$('#box'),使用了元素查找的方式,并且也做了浏览器兼容的处理,查找完成之后,便返回了this。 创建元素解决了,下面是查询元素。
元素查找
else if ( !context || context.jquery ) {
当没有上下文时,直接在document的jQ对象下调用find方法。当context存在时,能够进入的条件就是context下含有jQuery属性,比如$('.class', $('div#box'))这样的写法就会进入该条件下。
而$('.class', document.getElementById('box'))这种的会进入下一个else,执行find操作。
$(function)
当传入function的方式还没有考虑到,当使用$(function() {})时,会调用jQ根对象下的ready方法,也就是文档加载完成后执行。这也就是为什么我们常常说,$(function(){})是$(document).ready(function(){})的一种简写方式。
最后剩下个if判断传入的selector的selector属性:
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
该方式是兼容$(jQ对象)这种方式的,这样也增加了$的容错性。 将所有的情况都考虑一遍,整个init函数就是这种情况:
init: function( selector, context, rootjQuery ) {
if ( !selector ) {
// $(""), $(null), $(undefined), $(false)
}
if ( selector.nodeType ) {
// $(DOMElement)
}
if ( typeof selector === "string" ) {
// $('<div>123'), $('<div>123</div>'), $('432<div>123</div>243'), $('#box'), $('#box', document)
// $('<div></div>', {title: '123'}), $('#box', $(document)), $('.class'), $('#box p')
if ( match && (match\[1\] || !context) ) {
// $('<div>123'), $('<div>123</div>'), $('432<div>123</div>243'), $('#box'), $('<div></div>', {title: '123'})
if ( match\[1\] ) {
// $('<div>123'), $('<div>123</div>'), $('432<div>123</div>243')
if ( rsingleTag.test( match\[1\] ) && jQuery.isPlainObject( context ) ) {
// $('<div></div>', {title: '123'})
}
} else {
// $('#box')
}
} else if ( !context || context.jquery ) {
// $('#box', $(document)), $('.class'), $('#box p')
} else {
// $('#box', document)
}
} else if ( jQuery.isFunction( selector ) ) {
// $(function)
}
if ( selector.selector !== undefined ) {
// $(jQobject)
}
}
目录