zoukankan      html  css  js  c++  java
  • jQuery.2.0.3源码分析01-整体架构思想

    一、jQuery($)命名空间

    为了避免声明了一些全局变量造成变量污染,使用立即执行函数形成jQuery($)独立的命名空间;

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

    二、jQuery的本质是什么?

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

    由jQuery的源码可知,jQuery的本质是一个函数(对象),是函数就应该有原型(prototype对象),但是,jQuery重置了该原型(prototype对象),也就是重新赋值,同时也将原型(prototype对象)赋值到jQuery的fn属性上,也添加了很多属性和方法,看源码:

    (function(window, undefined){
    
        var jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context, rootjQuery );
        };
        
    var core_version = "2.0.3"
    jQuery.fn
    = jQuery.prototype = { jquery: core_version, // 存放jQuewry版本 constructor: jQuery, // 构造函数
    init:
    function( selector, context, rootjQuery ) { } } })(window)

    三、jQuery对象是什么,也就是$()返回了什么?

    我们都知道jQuery可以通过选择器获取DOM对象,但是jQuery返回的DOM对象和原生DOM对象(如通过document.getElementById获取的DOM对象)是不一样的,但是可以相互转换,如可以通过$().get()方法或者$()[]将jQuery DOM对象转换成原生DOM对象,也可以通过$(原生DOM对象)将原生DOM对象转换成jQuery DOM对象。

    但是jQuery对象是什么($()返回了什么),如图用原生方法和jQuery方法获取一个id为div1的元素:

    通过document.getElementsByClassName和通过$方法获取class为div的元素:

    $()返回的值为什么是这样的呢?其实我们可以看源码$()的返回值也就是构造函数init的返回值:

    (function(window, undefined){
    
        var jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context, rootjQuery );
        };
        var core_version = "2.0.3"
        jQuery.fn = jQuery.prototype = {
    
                 jquery: core_version, // 存放jQuewry版本
                 constructor: jQuery,  // 构造函数
                 init: function( selector, context, rootjQuery ) {
                  
                 }
    
        }
        
        
    })(window)

    返回值是执行一个函数,

    return new jQuery.fn.init( selector, context, rootjQuery );

    ,是jQuery原型重置后原型上的一个函数,而且通过new创建的,那么其实$()的返回值也就是init这个函数的返回值。通过new执行的函数也就是构造函数的执行,init是一个构造函数(虽然首字母没有大写),也就是说jQuery DOM对象($()的返回值)是init构造函数的一个实例,该实例继承了构造函数init原型(prototype对象)上的所有属性和方法(构造函数init原型上有哪些属性和方法呢?,这一点很重要,先卖个关子。我们先来看一下jQuery源码上构造函数init返回了什么:

      1 (function(window, undefined){
      2 
      3     var jQuery = function( selector, context ) {
      4         return new jQuery.fn.init( selector, context, rootjQuery );
      5     };
      6 
      7     jQuery.fn = jQuery.prototype = {
      8 
      9              jquery: core_version, // 存放jQuewry版本
     10              constructor: jQuery,  // 构造函数
     11              init: function( selector, context, rootjQuery ) {
     12                 var match, elem;
     13 
     14         // HANDLE: $(""), $(null), $(undefined), $(false)
     15         if ( !selector ) {
     16             return this;
     17         }
     18 
     19         // Handle HTML strings
     20         if ( typeof selector === "string" ) {
     21             if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
     22                 // Assume that strings that start and end with <> are HTML and skip the regex check
     23                 match = [ null, selector, null ];
     24 
     25             } else {
     26                 match = rquickExpr.exec( selector );
     27             }
     28 
     29             // Match html or make sure no context is specified for #id
     30             if ( match && (match[1] || !context) ) {
     31 
     32                 // HANDLE: $(html) -> $(array)
     33                 if ( match[1] ) {
     34                     context = context instanceof jQuery ? context[0] : context;
     35 
     36                     // scripts is true for back-compat
     37                     jQuery.merge( this, jQuery.parseHTML(
     38                         match[1],
     39                         context && context.nodeType ? context.ownerDocument || context : document,
     40                         true
     41                     ) );
     42 
     43                     // HANDLE: $(html, props)
     44                     if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
     45                         for ( match in context ) {
     46                             // Properties of context are called as methods if possible
     47                             if ( jQuery.isFunction( this[ match ] ) ) {
     48                                 this[ match ]( context[ match ] );
     49 
     50                             // ...and otherwise set as attributes
     51                             } else {
     52                                 this.attr( match, context[ match ] );
     53                             }
     54                         }
     55                     }
     56 
     57                     return this;
     58 
     59                 // HANDLE: $(#id)
     60                 } else {
     61                     elem = document.getElementById( match[2] );
     62 
     63                     // Check parentNode to catch when Blackberry 4.6 returns
     64                     // nodes that are no longer in the document #6963
     65                     if ( elem && elem.parentNode ) {
     66                         // Inject the element directly into the jQuery object
     67                         this.length = 1;
     68                         this[0] = elem;
     69                     }
     70 
     71                     this.context = document;
     72                     this.selector = selector;
     73                     return this;
     74                 }
     75 
     76             // HANDLE: $(expr, $(...))
     77             } else if ( !context || context.jquery ) {
     78                 return ( context || rootjQuery ).find( selector );
     79 
     80             // HANDLE: $(expr, context)
     81             // (which is just equivalent to: $(context).find(expr)
     82             } else {
     83                 return this.constructor( context ).find( selector );
     84             }
     85 
     86         // HANDLE: $(DOMElement)
     87         } else if ( selector.nodeType ) {
     88             this.context = this[0] = selector;
     89             this.length = 1;
     90             return this;
     91 
     92         // HANDLE: $(function)
     93         // Shortcut for document ready
     94         } else if ( jQuery.isFunction( selector ) ) {
     95             return rootjQuery.ready( selector );
     96         }
     97 
     98         if ( selector.selector !== undefined ) {
     99             this.selector = selector.selector;
    100             this.context = selector.context;
    101         }
    102 
    103         return jQuery.makeArray( selector, this );
    104              }
    105 
    106     }
    107     
    108     
    109 })(window)
    View Code

     源码有点复杂,因为jQuery的选择器功能也就是对DOM操作得功能非常的强大,我们先弱化jQuery的选择器功能,让jQuery只能通过id获取DOM元素,如,改写jQuery的源码如下:

     1 (function(window, undefined){
     2 
     3     var jQuery = function( selector, context ) {
     4         return new jQuery.fn.init( selector, context, rootjQuery );
     5     };
     6 
     7     jQuery.fn = jQuery.prototype = {
     8 
     9          jquery: core_version, // 存放jQuewry版本
    10          constructor: jQuery,  // 构造函数
    11          init: function( selector, context, rootjQuery ) {
    12 
    13              var match, elem;
    14 
    15               // HANDLE: $(""), $(null), $(undefined), $(false)
    16             if ( !selector ) {
    17                 return this;
    18             }else {
    19                 elem = document.getElementById( selector.replace('#','') );
    20 
    21                 // Check parentNode to catch when Blackberry 4.6 returns
    22                 // nodes that are no longer in the document #6963
    23                 if ( elem && elem.parentNode ) {
    24                     // Inject the element directly into the jQuery object
    25                     this.length = 1;
    26                     this[0] = elem;
    27                 }
    28 
    29                 this.context = document;
    30                 this.selector = selector;
    31                 return this;
    32             }
    33 
    34          }
    35 
    36     }
    37     
    38     
    39 })(window)
    View Code

    由源码可以知道,返回值是 this 。这个this指的是什么?如果不知道可以去了解一下 new + 构造函数 的知识,其实通过new调用构造函数,构造函数中的this就是指向该构造函数的实例,在这里也就是指向init构造函数的实例。看一下具体是什么:

    init其实也就是一个json对象,key值为0的value值是原生DOM对象,也就能解析为什么可以通过$()[0]就能将JQuery对象转换成原生DOM对象了。 init实例对象还存放了很多信息,如length、选择器、上下文document,其实在实际的jQuery中,$()传入的参数不同,init存放的信息也各不相同,自己可以去了解一下,比如上图,$的参数为空和传入id,init实例的数据就不同,又比如传入class和id又有所不同。

    尽管$方法传入的参数不同,init实例也有所不同,但是有一点是相同的,也就是都继承构造函数init的原型对象,如:

    我们都知道,jQuery对象有很多方法,如$().attr、$().addClass、$().removeClass等等.......,这些方法哪里来的呢,很明显是通过继承构造函数init原型上的,那么构造函数init原型上为什么有这些方法,按理来说构造函数是既然是一个函数,那么它肯定是继承了Object原型上的方法,但是Object原型上又没有这些方法,哪里来的呢?请往下看。

    四、重置构造函数init的原型对象

     上jQuery源码,看构造函数init的原型对象:

    还有上面提过的:

    也就是这样:

    也就是说构造函数init的实例继承了jQuery原型上的所有属性和方法,同时将init实例的构造函数指向jQuery(造假技术一流),如:

    在所有的jQuery对象中都可以找到jQuery的版本号和构造函数,同时也能解释为什么jQuery对象上面会有这么多的方法了。

    五、jQuery插件机制

    jQuery为开发插件提拱了两个方法,分别是:

    jQuery.fn.extend(object); 给jQuery对象添加方法。
    
    jQuery.extend(object); 为扩展jQuery类本身.为类添加新的方法,可以理解为添加静态方法(工具方法)。

    这两个方法都接受一个参数,类型为Object,Object对应的"名/值对"分别代表"函数或方法体/函数主体"。看源码:

    jQuery.extend = jQuery.fn.extend = function() {
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;
    
        // Handle a deep copy situation
        if ( typeof target === "boolean" ) {
            deep = target;
            target = arguments[1] || {};
            // skip the boolean and the target
            i = 2;
        }
    
        // Handle case when target is a string or something (possible in deep copy)
        if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
            target = {};
        }
    
        // extend jQuery itself if only one argument is passed
        if ( length === i ) {
            target = this;
            --i;
        }
    
        for ( ; i < length; i++ ) {
            // Only deal with non-null/undefined values
            if ( (options = arguments[ i ]) != null ) {
                // Extend the base object
                for ( name in options ) {
                    src = target[ name ];
                    copy = options[ name ];
    
                    // Prevent never-ending loop
                    if ( target === copy ) {
                        continue;
                    }
    
                    // Recurse if we're merging plain objects or arrays
                    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 : {};
                        }
    
                        // Never move original objects, clone them
                        target[ name ] = jQuery.extend( deep, clone, copy );
    
                    // Don't bring in undefined values
                    } else if ( copy !== undefined ) {
                        target[ name ] = copy;
                    }
                }
            }
        }
    
        // Return the modified object
        return target;
    };
    可以看到这么一句:
    jQuery.extend = jQuery.fn.extend

    同时指向一个函数却实现不同的功能,可以思考一下为什么?其实很简单,加上函数里面this的指向问题,如果是jQuery.extend({}),函数里面的this指向的是jQuery,如果是

    jQuery.fn.extend({}),函数里面的this指向jQuery.fn,这样就能知道到底是往jQuery拓展类本身添加方法还是往jQuery的原型上添加方法同时继承个jQuery对象了。

    方法就是往jQuery.prototype上添加方法,如:

     向jquery拓展类本身添加工具方法,如:

    六、$.noConflict()

    看源码,这里jQuery命名空间最底部的一段:

    个人觉得这是一种不使用return方式形成的闭包方式,return方式是函数执行将返回值赋值到一个全局变量上,而jQuery的做法是直接在里面赋值都window上,其作用是一样的,如果使用return方式,return jQuery到外层全局变量也是可以的,但是,jQuery作者为什么没有这么做呢?其实有很深沉的含义在里面,window.jQuery和window.$也可能造成变量污染,比如和prototype库中的window.$形成污染(或者同时引入jQuery的不同版本),那该如何解决呢?那就的靠jQuery.noConflict()来解决了。解决冲突是有前提的:

    1、其他使用jQuery、$变量的库必须在引入jQuery之前引入。(因为如果在jQuery之后引入,就直接覆盖了jQuery了)

     那么jQuery是如何解决和其他库的冲突的呢?看源码:

    在jQuery命名空间的顶部先把全局作用域window中的jQuery和$先赋值给jQuery命名空间中的局部变量存放起来(如果不存在,_jQuery和$的值就是undefined),如果存在冲突,那么再使用jQuery或者$之前,先执行$.noConflict(),可以看源码这个函数做什么了:

    如果全局作用域中(window上)的$和jQuery命名空间上的jQuery是全等的,那么将全局作用域中(window上)的$赋值给命名冲突库中的$(已存放到_$中),如果deep为true,说明jQuery也有冲突,也将jQuery从新赋值给命名冲突库中的jQuery(已存放到_jQuery中),最后return jQuery,我们可以对jQuery中的$重新赋值,如:

    在不存在冲突的情况下执行了$.noConflict()后,$不能再使用,它的值是undefined。至此,可以说jQuery实现了对全局作用域的零变量污染。

    七、总结

     1、jQuery的本质是一个函数,但是不作为构造函数使用,而是返回 一个构造函数init的实例,也就是jQuery对象。

     2、jQuery函数重置了它的原型(prototype对象),重新定义,同时作为返回值的构造函数init的实例的原型也被重置,指向jQuery的原型,所有构造函数init的实例继承了jQuery原型上的所有属性和方法。

     以上是本人的一下理解,有错欢迎指出,网上有很多分析jQuery源码的,更多请参考:

    1、https://www.zhihu.com/question/20521802

    2、by Aaron:http://www.cnblogs.com/aaronjs/p/3279314.html

  • 相关阅读:
    struts2 easyui实现datagrid的crud
    Codeforces 10A-Power Consumption Calculation(模拟)
    Java5新特性之枚举
    [总结]视音频编解码技术零基础学习方法
    (三 )kafka-jstorm集群实时日志分析 之 ---------jstorm集成spring 续(代码)
    codeforces Looksery Cup 2015 H Degenerate Matrix 二分 注意浮点数陷阱
    NHibernate3剖析:Query篇之NHibernate.Linq增强查询
    hdoj 1013Digital Roots
    小Y的难题
    【前端JS】radio 可单选可点击取消选中
  • 原文地址:https://www.cnblogs.com/yanmuxiao/p/8684473.html
Copyright © 2011-2022 走看看