jquery源码分析(二)——结构

再来复习下整体架构:

jQuery源码分析(基于 jQuery 1.11 版本,共计8829行源码)

(21,94)                定义了一些变量和函数jQuery=function(){}

(96,280)        给jQuery添加一些方法和属性,jQuery.fn=jQuery.prototype
(285,347)        extend:        jQuery的一些继承方法        更容易进行后续的扩展                                                
(349,817)        jQuery.extend(): 扩展一些工具方法
(877,2856)        Sizzle:复杂选择器的实现
(2880,3042) Callbacks:回调对象——》对函数的统一管理
(3043,3183)        Deferred:延迟对象——》对异步的统一管理
(3184,3295)        support:功能检测
(3308,3652)        data():数据缓存
(3653,3797)        queue():队列管理
(3083,4299)        attr(),prop() val() addClass():属性操作
(4300,5128)        on() trigger():事件的相关方法
(5140,6057)        DOM操作:添加 删除 获取 包装 筛选
(6058,6620)        css():针对样式的操作
(6621,7854) 提交的数据和Ajax()的操作:ajax() load() getJSON()
(7855,8584)        animate():运动的方法
(8585,8792)        offset:位置与尺寸的方法 
(8804,8821)        jQuery支持模块化的模式 module
(8826)                对外提供jQuery对象接口:window.jQuery=window.$=jQuery;

接下来看看jquery对象是怎么创建。

(function( window, undefined ) {
    var jQuery = (function() {
       // 构建jQuery对象
       var jQuery = function( selector, context ) {
           return new jQuery.fn.init( selector, context, rootjQuery );
       }

       // jQuery对象原型
       jQuery.fn = jQuery.prototype = {
           constructor: jQuery,    }
       init = jQuery.fn.init = function( selector, context, rootjQuery ) {
              // selector有以下7种分支情况:
              // DOM元素
              // body(优化)
              // 字符串:HTML标签、HTML字符串、#id、选择器表达式
              // 函数(作为ready回调函数)
              // 最后返回伪数组
       }

       init.prototype = jQuery.fn;// 合并内容到第一个参数中,后续大部分功能都通过该函数扩展
       // 通过jQuery.fn.extend扩展的函数,大部分都会调用通过jQuery.extend扩展的同名函数
       jQuery.extend = jQuery.fn.extend = function() {};

       // 在jQuery上扩展静态方法
       jQuery.extend({
           // ready bindReady
           // isPlainObject isEmptyObject
           // parseJSON parseXML
           // globalEval
           // each makeArray inArray merge grep map
           // proxy
           // access
           // uaMatch
           // sub
           // browser
       });

        // 到这里,jQuery对象构造完成,后边的代码都是对jQuery或jQuery对象的扩展
       return jQuery;

    })();

    window.jQuery = window.$ = jQuery;
})(window);

从这段代码可以看出:

1、 jQuery对象不是通过 new jQuery 创建的,而是通过 new jQuery.fn.init 创建的。

2、jQuery对象就是jQuery.fn.init对象( new jQuery.fn.init( selector, context, rootjQuery );)

3、 jQuery.fn指向了jquery的原型(实际上是用属性名fn代替prototype,为后面编程使用方便,要不然多次使用prototype会很绕):

jquery 原型定义了jquery对象初始化init的一系列方法。

4、接下来,定义在jQuery.fn.init,后面又加上这句init.prototype = jQuery.fn :即jQuery.fn.init的原型也指向了jQuery的原型,有点绕哈,合并下:

jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype,实例说明一下,

我们使用new jQuery.fn.init()创建的对象的原型对象就是jQuery.fn,这样你就可以在jquery对象就可以直接调用jquery对象原型上挂载的方法了。

5、重点来了:jQuery.extend = jQuery.fn.extend = function() {};

jQuery之所以如此强大,就是它能很容易实现类方法(虽然javascript没有类的概念,但这里还是用类来理解下,希望不要误解到读者)和实例方法扩展。

下面分析下jQuery实例对象这么创建的。

init = jQuery.fn.init = function( selector, context ) {
        var match, elem;

        // HANDLE: $(""), $(null), $(undefined), $(false)
        if ( !selector ) {
            return this;
        }

        // Handle HTML strings
        if ( typeof selector === "string" ) {
            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 ];

            } else {
                match = rquickExpr.exec( selector );
            }

            // Match html or make sure no context is specified for #id
            if ( match && (match[1] || !context) ) {

                // HANDLE: $(html) -> $(array)
                if ( match[1] ) {
                    context = context instanceof jQuery ? context[0] : context;

                    // scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    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 ] ) ) {
                                this[ match ]( context[ match ] );

                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

                // HANDLE: $(#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 ) {
                        // Handle the case where IE and Opera return items
                        // by name instead of 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;
                }

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

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor( context ).find( selector );
            }

        // HANDLE: $(DOMElement)
        } else if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;

        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) {
            return typeof rootjQuery.ready !== "undefined" ?
                rootjQuery.ready( selector ) :
                // Execute immediately if ready is not present
                selector( jQuery );
        }

        if ( selector.selector !== undefined ) {
            this.selector = selector.selector;
            this.context = selector.context;
        }

        return jQuery.makeArray( selector, this );
    };

分析上面代码:

1、jQuery.fn.init的功能是对传进来的selector参数进行分析,进行各种不同的处理,然后创建jQuery实例对象。

2、arguments:选择器和使用的上下文环境(比较生涩:举个粟子,比如在地图上找深圳这个地方,选择器好比深圳这个地名,熟悉的人可能一下子用经纬度定位了,东经­113°46‘~114°37‘,北纬22°27‘~22°52‘(参数context都不用传了^^),对歪国仁来说,可能第一眼的先定位到我们的公鸡版图:也就是context:china,这样搜索速度就加快了。对国人(闭眼也能找着祖国哈^^)来说直接找广东—靠海—靠香港:也就是context:广东)。

3、接下来看看怎么分析selector参数类型以及相应的做了什么处理。

问题来了,看到这个正则这么长有没有要晕,要吐的感觉,

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,这里写的什么。呃这个暂时不解释,后面专门奉上一个正则表达专题供大家解读…………。

(1)  $(""), $(null), $(undefined), $(false);这几种情况直接返回 this,这里this是什么呢? 注意不是JQuery本身,而是jQuery.fn.init

(2)、字符串类型

a:HTML标签形式的——$(<>):通过document.createElement 创建节点。类似 $("<div></div>")、$("<div>aaa</div><div>aaa</div>") 两种情况;

而其他情况如:$(“</div>111”)、$("#id")、$(".class")、$("div")、$("#id .class div"),其中requickExpr匹配的是$(“</div>111”)、$("#id")。如果传入的是$(“</div>111”),则match=["</div>111","</div>",null];如果传入的是$("#id"),则match=["#id",null,“id”]。

b:HTML字符串——$(html) -> $(array):创建DOM并扩充到jQuery对象

c:#id——$(#id):直接通过document.getElementById获取dom节点。

d:选择器表达式——$(expr, $(...)):

(3)、DOMElement:直接返回将该对象,只是修改了context属性和length属性。

(4)、function:用过jquery基本上都会初始化代码写到 $(function(){……})里,就是DOMLoaded 加载完毕之后回调执行改匿名函数。

(5)、slector.slector = undefined 这个眼戳暂时没看懂,有谁看懂提下谢谢 。

上面涉及到的函数这里先提前脑补一下:$.parseHTML 、$.merge、$.isPlainObject(

$.parseHTML :将字符串转换为存储DOM节点的数组 。第一个参数为传入的字符串,第二个为指定的根节点,第三个是boolean值 (“<script></script>”是否可以转化为<script>标签),默认为false,不转换。

$.merge:合并两个数组,在jQuery内部不仅可以合并数组,也可以合并类数组。

$.isPlainObject():判断传入的参数是否是由 {}或new Object 创建的对象。

下面看看jQuery原型挂载了什么?

jQuery.fn = jQuery.prototype = {
    // The current version of jQuery being used
    jquery: version,

    constructor: jQuery,

    selector: "",

    length: 0,

    toArray: function() {
        return slice.call( this );
    },

    get: function( num ) {
        return num != null ?
            ( num < 0 ? this[ num + this.length ] : this[ num ] ) :

            slice.call( this );
    },

    pushStack: function( elems ) {

        var ret = jQuery.merge( this.constructor(), elems );

        ret.prevObject = this;
        ret.context = this.context;

        return ret;
    },

    each: function( callback, args ) {
        return jQuery.each( this, callback, args );
    },

    map: function( callback ) {
        return this.pushStack( jQuery.map(this, function( elem, i ) {
            return callback.call( elem, i, elem );
        }));
    },

    slice: function() {
        return this.pushStack( slice.apply( this, arguments ) );
    },

    first: function() {
        return this.eq( 0 );
    },

    last: function() {
        return this.eq( -1 );
    },

    eq: function( i ) {
        var len = this.length,
            j = +i + ( i < 0 ? len : 0 );
        return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
    },

    end: function() {
        return this.prevObject || this.constructor(null);
    },

    push: push,
    sort: deletedIds.sort,
    splice: deletedIds.splice
};

1、先来看这三个属性:push、sort、splice 都是new Array()对象下方法。仅供jQuery内部使用(私有方法),与jQuery外部工具方法(对外可使用)不同

2、first、last、eq这几个方法都是都是又来获取数组某个元素的

3、重点来了:pushStack在jQuery中使用频率特别高,它是干嘛的?只看这段代码。只能知道 它调用了 jQuery.merge,合并了类数组。

jQuery.extend = jQuery.fn.extend = function() {
    var src, copyIsArray, copy, name, options, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    if ( typeof target === "boolean" ) {
        deep = target;

        target = arguments[ i ] || {};
        i++;
    }

    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
        target = {};
    }

    if ( i === length ) {
        target = this;
        i--;
    }

    for ( ; i < length; i++ ) {
        if ( (options = arguments[ i ]) != null ) {
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                if ( target === copy ) {
                    continue;
                }

                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    target[ name ] = jQuery.extend( deep, clone, copy );

                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }
    return target;
};

1、功能:jQuery.extend(object); 为扩展jQuery类本身.为类添加新的方法。jQuery.fn.extend(object);给jQuery实例对象添加方法。

2、来看看它的如何使用的:

(1)、var newSrc=$.extend({},src1,src2,src3...)

(2)、var newSrc=$.extend(false,{},src1,src2,src3...)//嵌套子对象不拷贝

(3)、var newSrc=$.extend(true,{},src1,src2,src3...)

从arguments可以看出,extend是为了合并两个或更多对象的属性到第一个对象中。

3、再来分析代码:看第一个逻辑意思是:如果第一个参数是布尔型的话,要实现扩展的对象target就是第二个参数,接下来再判断此时target是否对象或函数,都不是则默认扩展到{}。

接下来两个for循环,第一个循环遍历后续所有对象,第二个循环 实现将每个对象属性的copy到第一个对象。看似很简单,问题来了,target的属性与需要copy对象属性一样时,怎么处理?是直接覆盖/重写,还是合并?这就涉及到了浅拷贝与深拷贝,那浅拷贝与深拷贝又是什么。

(1)、js对象浅拷贝简单的赋值就是浅拷贝。因为对象和数组在赋值的时候都是引用传递。赋值的时候只是传递一个指针。也就是说遇到同名属性时直接覆盖/重写。

(2)、因为对象相对较为复杂,所以我们先来看对数组的深拷贝的问题

来看个例子,说明一下

extend(boolean,dest,src1,src2,src3...)

第一个参数boolean代表是否进行深度拷贝,其余参数和前面介绍的一致,什么叫深层拷贝,我们看一个例子:

var result=$.extend( true,  {},      { name: "John", location: {city: "Boston",county:"USA"} },      { last: "Resig", location: {state: "MA",county:"China"} } ); 

我们可以看出src1中嵌套子对象location:{city:"Boston"},src2中也嵌套子对象location:{state:"MA"},第一个深度拷贝参数为true,那么合并后的结果就是:

result={name:"John",last:"Resig",location:{city:"Boston",state:"MA",county:"China"}} ////浅拷贝,对copy对象同名属性值类型为数组或对象的属性进行合并

也就是说它会将src中的嵌套子对象也进行合并,

而如果第一个参数boolean为false,我们看看合并的结果是什么,如下:

var result=$.extend( false, {},  { name: "John", location:{city: "Boston",county:"USA"} },  { last: "Resig", location: {state: "MA",county:"China"} } ); 

那么合并后的结果就是:

  result={name:"John",last:"Resig",location:{state:"MA",county:"China"}}//浅拷贝,对copy对象同名属性值类型为数组或对象的属性直接覆盖/重写。

来看下深拷贝和前拷贝怎么实现的:

if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    target[ name ] = jQuery.extend( deep, clone, copy );

                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }

浅拷贝:target[ name ] = copy;直接覆盖和容易理解。深拷贝稍显复杂,首先判断是数组还是对象,在对嵌套子对象递归调用$.extend方法。

jquery.fn比jquery.prototype短,prototype这玩意不能优化, jquery插件都要引用jquery.fn的,我想这是jquery.fn的作用。

刚开始这段是为模块化编程(如seaJS、requiredJS)输出预留接口,这里不是jquery重点,就不细道来了(有点懒^^)。

 if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
时间: 2024-09-30 16:31:26

jquery源码分析(二)——结构的相关文章

jQuery源码分析:源码结构与核心函数

jQuery源码分析-03构造jQuery对象-源码结构和核心函数 jQuery.fn和jQuery.prototype区别

jQuery源码分析系列(33) : AJAX中的前置过滤器和请求分发器

jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求,分别是: 1.前置过滤器 jQuery. ajaxPrefilter 2.请求分发器 jQuery. ajaxTransport, 3.类型转换器 ajaxConvert 源码结构: jQuery.extend({ /** * 前置过滤器 * @type {[type]} */ ajaxPrefilter: addToPrefiltersOrTransports(prefilters), /** * 请求分发器 *

Jquery源码分析

1.概述 jQuery是一个非常优秀的Js库,与prototype,YUI,Mootools等众多的Js类库相比,它剑走偏锋,从web开发最实用的角度出发,抛除了一些中看但不实用的东西,为开发者提供一个短小精悍的类库.由于其个短小精悍,使用简单方便,性能相对高效.众多的开发者都选择Jquery来进行辅助的web开发. 在使用jquery时开发,我们也会时常碰到许多的问题,但是jquery的代码很晦涩,难起看懂,当开发时出现了问题,看不懂源码,不知道如何去排错. John Resig,Jquery

jQuery源码分析系列(36) : Ajax - 类型转化器

什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的响应只有 responseText与responseXML 二种 所以现在我要定义dataType为jsonp,那么所得的最终数据是一个json的键值对,所以jQuery内部就会默认帮你完成这个转化工作 jQuery为了处理这种执行后数据的转化,就引入了类型转化器,如果没有指定类型就依据响应头Con

jQuery源码分析系列(39) : 动画队列

data函数在jQuery中只有短短的300行代码,非常不起点 ,剖析源码的时候你会发现jQuery只要在有需要保存数据的地方无时无刻不依赖这个基础设施 动画会调用队列,队列会调用data数据接口还保存队列里面的的动画数据 所以我们在自习回顾下关于数据缓存 //These may be used throughout the jQuery core codebase //存数据的 //用户使用 data_user = new Data(); //存储对象 //jQuery内部私有 //用来存事件

jQuery源码分析系列(35) : Ajax - jsonp的实现与原理

ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本 json核心就是:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了. jquery ext dojo这类库的实现手段其实大同小异 在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img.iframe.s

jQuery源码分析系列(37) : Ajax 总结

综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery. ajaxTransport 类型转换器 ajaxConvert 为了整体性与扩展性考虑,把整个结构通过Deferred实现异步链式模型,Promise对象可以轻易的绑定成功.失败.进行中三种状态的回调函数,然后通过在状态码在来回调不同的函数就行了 出于同源策略考虑,存在跨域问题,所以ajax内部

[转]jQuery源码分析系列

文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaron/jQuery 正在编写的书 - jQuery架构设计与实现 本人在慕课网的教程(完结) jQuery源码解析(架构与依赖模块) 64课时 jQuery源码解析(DOM与核心模块)64课时 jQuery源码分析目录(完结) jQuery源码分析系列(01) : 整体架构 jQuery源码分析系列(

[转] jQuery源码分析-如何做jQuery源码分析

jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书>结合起来进行学习.推荐读读这本书,你可以从这里和这里下载. 第一部分:检视阅读 1. 收集参考资料:官方文档.书籍.百度/谷歌,专题/博客等,快速的浏览,对涉及的知识点.范围.深度.是否有参考意义等有大致的了解和判断,知道这些文章的作者想要解释或解决什么问题. 第二部分:分析阅读 2. 细读官方文档,官方有非