jQuery源码分析3--jQ原型1

2017年07月02日Web前端

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')、$('

')、$('
111
')、$(function(){})等。这些方式可以细分为创建元素和查找元素。

处理字符串

if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {

该方式查找前后都是标签的html字符串,像$('

')、$('
111
')这些,而$('
123')这种是不会进入if的。if中得到了这样的一个match数组:

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的有:$('

')、$('
111
')、$('
123'),$('#box')要是能进入if的话,创建时的上下文必须不给赋值。这样一判断之后,是创建元素还是查找元素,大致已经被区分开了。

创建元素

随后使用 if ( match[1] ) { 来区分掉刚才上下文为false的id查找字符串。因此,能够进入if的都是创建元素的字符串。接下来,首先是对context的赋值,保证该context是原生的dom对象,并且对doc变量进行赋值。随后调用jQ的工具方法parseHTML将匹配到的HTML字符串转化为jq对象。如何转化的,到parseHTML定义的地方在叙述。

注:$('

123')得到的元素不会带123,是因为正则匹配的时候,match第二个值是
,所以创建的时候,只是创建了div元素,并没有赋值。

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)
    }
}