我爱编程

jQuery构造函数

2017-10-25  本文已影响0人  我爱吃烤鸡翅

前言

本篇文章较长,其中包含了对jQuery源码的分析,如果不感兴趣的同学可以直接查看参数类型部分,感兴趣的同学可以继续阅读下面源码解析,如果内容有误,还望指出。

背景

最近的工作需要处理IE8的兼容性问题,但是对于大量的工作任务,使用原生js实现功能,并且兼容IE8是很费时费力的事情,因此为了提高工作效率,JQuery框架便成为了不二选择,于是我便开始学习能提高开发效率的jQuery框架,该专题主要记录学习过程中的问题和总结所需要掌握的知识点,以及对于jQuery源码的分析与理解。文章参考了《jQuery技术内幕》以及网络上其他大牛的博客,旨在分享与记录,如有错误还望指出。

初识jQuery(构造函数)

在使用jQuery的过程中,我们首先就要构造jQuery对象,否则无法调用jQuery的方法,但在构造jQuery对象的过程中,我们发现同一个$()方法却可以传入多种不同的参数,于是以下便对参数进行总结以及分析:

参数类型:

1.$(element)
传入参数是一个DOM对象的情况下,则将DOM对象封装成一个jQuery对象并返回。
2.$(selector[,context])
selector为jQuery的css选择器,context是个可选参数,其表示查询的上下文及查询范围,如果不指定则表示全局查询。
3.$(html[,props])
html为html代码,jQuery会解析html并且使用其创建DOM节点,其中html可分为单独标签以及复杂代码片段,如<p/>或者<p></p>就是单独标签,<p>123</p>则是复杂片段,两种格式的DOM节点创建方式不同。前者是使用document.createElement()方式创建,后者使用innerHTML的方式创建。props则是设置创建的DOM节点的属性。
4.$(object)
object为普通javascript对象,则返回一个封装到jQuery中的对象,返回对象可以使用jQuery的方法。
5.$(callback)
callback为函数,当传入参数为函数时,会将传入函数绑定到ready时间上,其提前于onload事件,在DOM文本解析完成时便触发,貌似绑定与DOMContentLoaded事件上(猜测,暂时不知道,待后续研究)。
6.$(jQuery object)
如果传入一个jQuery对象,则创建该jQuery对象的一个副本并返回,副本与传入的jQuery对象引用完全相同的DOM元素。
7.$()
传入空对象,则返回一个空的jQuery对象。

源码分析:

通过上述参数分类,我们知道了jQuery的构造函数的强大,接下来我们就通过源码来分析上述不同参数类型在jQuery内部的处理方式,以便与我们进一步加深对于jQuery的理解。
首先我们从jQuery源码知道jQuery的所有代码都封装在一个自执行函数中,函数代码如下:

(function(window,undefined){
  ...
})(window);

上述代码只传入window对象,而内部存在一个undefined参数是为了确保undefined未被重赋值,保证jQuery内部代码的undefined的正确性。(undefined的重赋值问题见此文)

然后jQuery内部使用一种特别的方式避免了外部使用new方法构造jQuery对象,代码如下:

jQuery = function( selector, context ) {
    //jQuery.fn实则是jQuery.prototype,init则是jQuery原型上的方法,用于构造jQuery对象
    return new jQuery.fn.init( selector, context, rootjQuery );
}

那么以上方法就存在一个问题使用init方法构造jQuery对象会导致jQuery的原型方法实例无法使用,因为实例的原型是init方法的原型而init方法上并没有任何方法。于是jQuery采取了一种极其机智的方法处理上述问题,代码如下:

jQuery.fn.init.prototype = jQuery.fn;

以上代码将jQuery的prototype赋值给init的prototype(注意此时jQuery.fn=jQuery.prototype),于是init方法的实例自然可以使用jQuery对象上的原型方法了。

--等等,说了这么多压根没提jQuery.fn.init方法的内部机制呀💢,别急,理解了上面的知识更有利于理解init内部机制,接下来就让我们看看jQuery.fn.init的源码吧。

代码:(解释均在代码注释上)

function( selector, context, rootjQuery ) {
    //selector可以是DOM对象,undefined,字符串,函数,JQuery对象,普通javaScript对象-有效,其他参数无效
    //context可传入,可不传入,DOM对象,JQuery对象,普通javaScript对象
    //rootJQuery是包含了document对象的JQuery对象,用于document.getElementById()查找失败,selector选择器未指定context或者selector是函数的情况
    
    var match, elem, ret, doc;
    
    //处理$(''),$(null),$(undefined),$(false)
    if ( !selector ) {
        return this;
    }

    //处理传入参数是DOMElement的情况。
    if ( selector.nodeType ) {//$(this)等DOM对象在此转换成JQuery对象
        this.context = this[0] = selector;
        this.length = 1;
        return this;
    }

    // 处理传入是HTML代码的情况
    if ( typeof selector === "string" ) {
        if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
            //由于IE6~IE8与标准浏览器对charAt方法的支持是一样的。
            //而使用String[]的方法取值在IE6~IE8下会返回undefined(注意必须是String对象而非字面量)
            //处理HTML代码开始是<,结束是>的情况
            match = [ null, selector, null ];
        } else {
            //rquickExpr=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/
            match = rquickExpr.exec( selector );
        }

        // Match html or make sure no context is specified for #id
        if ( match && (match[1] || !context) ) {
            //如果match存在,且match[1],或者match[2]且context上下文范围为undefined的情况下执行下列代码
            // 处理: $(html) -> $(array)
            if ( match[1] ) {
                //如果match[1]表示是创建dom节点的话,先修正context对象
                context = context instanceof jQuery ? context[0] : context;
                doc = ( context && context.nodeType ? context.ownerDocument || context : document );

                // scripts is true for back-compat
                //parseHTML对单一标签内容通过createElement方法创建对象,并返回一个包含该对象的数组。
                //jQuery将对HTML代码的复杂对象与非复杂对象处理都放在了parseHTML函数中,感兴趣的同学可下去继续研究,我也会在以后对该函数进行分析。
                selector = jQuery.parseHTML( match[1], doc, true );
                
                if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
                //在context是props属性时,将属性赋值到新生成的DOM元素上
                    this.attr.call( selector, context, true );
                }

                return jQuery.merge( this, selector );

            // 处理: $(#id)
            } else {
                //查找id对应元素                  
                elem = document.getElementById( match[2] );

                //检测parentNode属性,因为黑莓4.6会返回已经在文档中不存在的节点
                if ( elem && elem.parentNode ) {
                    
                    //在IE7和IE6中有可能按照name查找了id的元素
                    if ( elem.id !== match[2] ) {
                        return rootjQuery.find( selector );
                    }

                    // Otherwise, we inject the element directly into the jQuery object
                    this.length = 1;
                    this[0] = elem;
                }

                this.context = document;
                this.selector = selector;
                return this;
            }

        // 处理: $(selector, $(...))
        } else if ( !context || context.jquery ) {
            return ( context || rootjQuery ).find( selector );

        // 处理: $(selector, context)
        // 相当于$(context).find(expr)
        } else {
            return this.constructor( context ).find( selector );
        }

    // 处理: $(function)
    } else if ( jQuery.isFunction( selector ) ) {
        return rootjQuery.ready( selector );//如果是一个函数则执行ready
    }

    if ( selector.selector !== undefined ) {
        //如果传入对象是JQuery对象
        this.selector = selector.selector;
        this.context = selector.context;
    }
    //在传入参数是非上述类型数据,而是如同Array,Number,JS原生对象的情况下,将直接置入当前jQuery实例中返回,以使上述数据可使用jQuery方法
    return jQuery.makeArray( selector, this );
}  

上述代码段便是jQuery的init方法内部机制,其中我都加上了解释与自己的理解,希望读者能够阅读并且在浏览器环境下通过开发者工具执行jQuery代码并加入断点,观测代码执行情况以进一步测试代码运行。

总结:

jQuery是一个非常棒的函数库,它帮助开发者减轻开发负担,解决兼容问题,提高开发效率,但是作为一个想要在JS方面有所成就的开发者,仅会使用是不够的,我们还要花费时间去学习该框架的内部机制,理解其代码风格,当质疑自己代码写得不好的时候,那就是因为你看的代码不够多,因此学习jQuery源码,可以让我们真正了解到自己与大师之间的差距。以后我也会进一步对jQuery代码进行研究,其中很多个大的模块如AJAX,选择器,DOM操作,事件代理等均是jQuery的经典之处,希望有兴趣的同学也可以自己研究深入。
行文仓促,如有错误,还望指出。🙏

上一篇下一篇

猜你喜欢

热点阅读