美文网首页我爱编程
Jquery源码解析(一)

Jquery源码解析(一)

作者: 我是上帝可爱多 | 来源:发表于2017-07-19 14:40 被阅读179次

    入门前端也一年了,从来没有仔细看过jquery的源码,最近一直在搞angular4,抽点时间写下这个

     (function (global,factory){
         
    })(typeof window != 'undefined' ? window : this , function(window,noGlobal){
    
    })
    

    这是jquery的入口,一个自执行函数,相信有基础的都懂。

    (function (global,factory){
         "use strict";
         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 );
        }
    })(typeof window != 'undefined' ? window : this , function(window,noGlobal){
    
    })
    

    // For environments that do not have a window with a document
    // (such as Node.js), expose a factory as module.exports.
    解释一下,如果是node环境,没有window对象和document就用commonjs,利用module.exports导出模块

    接下来大家比较关注应该是这个factory工厂函数,我们来看一下里面的内容

    function(window,noGlobal){
         var version = "3.2.1",
         // Define a local copy of jQuery
        jQuery = function( selector, context ) {
                return new jQuery.fn.init( selector, context );
        },
    }
    

    装个逼,我用的是最新的Jq(3.2.1)
    看到了关键字Jquery,这个方法一看就是获取dom节点的,有2个参数:
    1.selector:选择器 可以是字符串 正则 等一系列的东东
    2.context:上下文 这个选择器是在那个节点下面找到的,默认是body

    jQuery = function( selector, context ) {
                return new jQuery.fn.init( selector, context );
        },
    var init = jQuery.fn.init = function( selector, context, root ) {
        
    };
    init.prototype = jQuery.fn;
    jQuery.fn = jQuery.prototype = {
    }
    

    这段代码如果原型链没有过关的同学看起来比较费劲,简单解释一下吧
    init.prototype = jQuery.fn = jQuery.prototype 这个连等都看的懂吧
    然后 init = jQuery.fn.init 那么

    new jQuery.fn.init() == new init() == new Jquery()
    其实这里大费周章 new jQuery.fn.init( selector, context ) 就是返回Jquery一个实例而已
    

    接下来我们比较关心的是 jQuery.fn.init = function( selector, context, root ) {
    };里面的代码

    var rootjQuery,
        rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
            init = jQuery.fn.init = function( selector, context, root ) {
            var match, elem;
                    if ( !selector ) {
                return this;
            }
    
            root = root || rootjQuery;
    
            // Handle HTML strings
            if ( typeof selector === "string" ) {
                
    
            // HANDLE: $(DOMElement)
            } else if ( selector.nodeType ) {
                this[ 0 ] = selector;
                this.length = 1;
                return this;
    
                    // HANDLE: $(function)
              } else if ( jQuery.isFunction( selector ) ) {
                return root.ready !== undefined ?
                    root.ready( selector ) :
                                   selector( jQuery );
            }
                   return jQuery.makeArray( selector, this ); //把获得的dom节点转成数组
        };
    
    

    可以看得出来源码里面获得节点分3种:
    1.selector 是字符串类型的
    2.selector 是dom节点类型
    3.selector 是函数类型
    这里重点讲解一下字符串是如何处理的

    var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ),
    rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;
    
    if ( typeof selector === "string" ) {
                if ( selector[ 0 ] === "<" &&
                    selector[ selector.length - 1 ] === ">" &&
                    selector.length >= 3 ) {
                            // 以html标签的形式
                    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;
    
                        // Option to run 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
                        ) );
    
                        return this;
    
                    // HANDLE: $(#id)  处理id选择器
                    } else {
                        elem = document.getElementById( match[ 2 ] );
    
                        if ( elem ) {
    
                            // Inject the element directly into the jQuery object
                            this[ 0 ] = elem;
                            this.length = 1;
                        }
                        return this;
                    }
    
                // HANDLE: $(expr, $(...))  利用内部的find方法寻找节点
                } else if ( !context || context.jquery ) {
                    return ( context || root ).find( selector );
    
                // (which is just equivalent to: $(context).find(expr)
                } else {
                    return this.constructor( context ).find( selector );
                }
    
        
            } 
    

    这一段看起来确实费劲,关于js里面强大的exec正则用法,大家自己可以研究
    context = context instanceof jQuery ? context[ 0 ] : context; 判断context是否是jquery实例

    下面来看jquery的原型里面有哪些代码

    var version = "3.2.1",
       jQuery = function( selector, context ) {
                return new jQuery.fn.init( selector, context );
            };
       jQuery.fn = jQuery.prototype = {
            jquery: version,
            constructor: jQuery,
            length: 0,
             toArray: function() {
                return slice.call( this );
            },
             get: function( num ){
              if ( num == null ) {
                    return slice.call( this );
                }
    
                return num < 0 ? this[ num + this.length ] : this[ num ];
            },
              pushStack: function( elems ) {
                var ret = jQuery.merge( this.constructor(), elems );
               ret.prevObject = this;
               return ret;
            },
    
           each: function( callback ) {
                return jQuery.each( this, callback );
            },
    
            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();
            },
            push: push,
            sort: arr.sort,
            splice: arr.splice
            // 这里的push  sort  splice就是数组的方法
        };
    

    看到了吧 这里涵盖了所有jquery对象的方法
    1.我们要把一个jq Dom对象转成数组,方便遍历,可以 $('').toArray()
    2.获取节点中某一个可以用 $('****').get(index)
    3.注意pushStack是一个很重要的函数,slice函数就用到了他
    4.可以通过$('
    ').slice(index) 截取部分dom元素
    5.each map等函数在这里也能看到。

    难道强大的Jquery原型上就这些方法,当然不是,我们接着看

    jQuery.extend = jQuery.fn.extend = function() {
            var options, name, src, copy, copyIsArray, clone,
                target = arguments[ 0 ] || {},
                i = 1,
                length = arguments.length,
                deep = false;
            //大家记得这个函数吗 $.extend(true,obj1,obj2,obj3)  第一个参数为true表示深克隆
            if ( typeof target === "boolean" ) {
                deep = target;
                target = arguments[ i ] || {};
                i++;
            }
    
            if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
                target = {};
            }
            // 当没有传递bool值时  参数只有一个时  也就是$.fn.extend({})
            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 = Array.isArray( copy ) ) ) ) {
    
                            if ( copyIsArray ) {
                                copyIsArray = false;
                                clone = src && Array.isArray( src ) ? src : [];
    
                            } else {
                                clone = src && jQuery.isPlainObject( src ) ? src : {};
                            }
    
                            target[ name ] = jQuery.extend( deep, clone, copy );
                         // 这种情况就实现了jquery原型方法的拓展 
                        } else if ( copy !== undefined ) {
                            target[ name ] = copy;
                        }
                    }
                }
            }
            return target;
        };
    

    有了以上代码,我们这样玩一哈

    $.fn.extend({
         say:function(){
            console.log(this.nodeName)
    },
        hasChild:function(ele){
            return $('ele this').length > 0
    }
    })
    

    上面2个方法适用于任何jq对象,每个jq对象都有say方法,打印自己的nodeType,还有个hasChild方法判断是否有某个子节点。
    那我们来看看jquery源码里面对原型上作了哪些扩展

    jQuery.fn.extend( {
        find: function( selector ) {
            var i, ret,
                len = this.length,
                self = this;
    
            if ( typeof selector !== "string" ) {
                return this.pushStack( jQuery( selector ).filter( function() {
                    for ( i = 0; i < len; i++ ) {
                        if ( jQuery.contains( self[ i ], this ) ) {
                            return true;
                        }
                    }
                } ) );
            }
    
            ret = this.pushStack( [] );
    
            for ( i = 0; i < len; i++ ) {
                jQuery.find( selector, self[ i ], ret );
            }
    
            return len > 1 ? jQuery.uniqueSort( ret ) : ret;
        },
        filter: function( selector ) {
            return this.pushStack( winnow( this, selector || [], false ) );
        },
        not: function( selector ) {
            return this.pushStack( winnow( this, selector || [], true ) );
        },
        is: function( selector ) {
            return !!winnow(
                this,
                    typeof selector === "string" && rneedsContext.test( selector ) ?
                    jQuery( selector ) :
                    selector || [],
                false
            ).length;
        },
         addBack: function( selector ) {
            return this.add( selector == null ?
                this.prevObject : this.prevObject.filter( selector )
            );
        }
    } );
    

    是不是看到了很多熟悉的jq方法,然而这些方法是被扩展进去的
    注意find()方法是递归查找,会一直找下去,效率并不高。
    addBack()方法的源码就在这

    <ul>
      <li>list item 1</li>
      <li>list item 2</li>
      <li class="third-item">list item 3</li>
      <li>list item 4</li>
      <li>list item 5</li>
    </ul>
    $( "li.third-item" ).nextAll().addBack()
      .css( "background-color", "red" );
    

    这里就不再赘述了,大家可以到源码中自行查找。
    下面说一个经常见得工具方法 $.extend()

    jQuery.extend( {
            isFunction: function( obj ) {
            return jQuery.type( obj ) === "function";
        },
    
        isWindow: function( obj ) {
            return obj != null && obj === obj.window;
        },
    
        isNumeric: function( obj ) {
    
            // As of jQuery 3.0, isNumeric is limited to
            // strings and numbers (primitives or objects)
            // that can be coerced to finite numbers (gh-2662)
            var type = jQuery.type( obj );
            return ( type === "number" || type === "string" ) &&
    
                // parseFloat NaNs numeric-cast false positives ("")
                // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
                // subtraction forces infinities to NaN
                !isNaN( obj - parseFloat( obj ) );
        },
    
        isPlainObject: function( obj ) {
            var proto, Ctor;
    
            // Detect obvious negatives
            // Use toString instead of jQuery.type to catch host objects
            if ( !obj || toString.call( obj ) !== "[object Object]" ) {
                return false;
            }
    
            proto = getProto( obj );
    
            // Objects with no prototype (e.g., `Object.create( null )`) are plain
            if ( !proto ) {
                return true;
            }
    
            // Objects with prototype are plain iff they were constructed by a global Object function
            Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
            return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
        },
           each: function( obj, callback ) {
            var length, i = 0;
    
            if ( isArrayLike( obj ) ) {
                length = obj.length;
                for ( ; i < length; i++ ) {
                    if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
                        break;
                    }
                }
            } else {
                for ( i in obj ) {
                    if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
                        break;
                    }
                }
            }
    
            return obj;
        }
    
    

    我们在日常开发中是不是会常用到上述工具函数,判断对象类型等
    最常用的还是$.each(obj,function()) 这个方法了吧
    也可以自定义一个工具函数来供自己使用

    $.extend({
        zhishu:function(num){
          var count = 0,flag = true;
          while(num--){
            for(var i =0; i<Math.sqrt(num);i++){
                if(num%i == 0) {
                   flag = !flag
                   break
            }
               count++    
           }
       }
    }
    })
    

    以上就写了方法查找某个数范围内的质数
    有一点也许大家还在像jquery和$是怎么联系到一起的。。。

    //当有一个也是以$开头的库与jquery冲突时 
    var _jQuery = window.jQuery,
           // Map over the $ in case of overwrite
        _$ = window.$;
    
    //这是获取之前定义的原始$
    jQuery.noConflict = function( deep ) {
           //如果现在定义的Jquery与之前不一样,那么之前的就得写成_$  _JQuery
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }
    
        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }
    
        return jQuery;
    };
    if ( !noGlobal ) {
        window.jQuery = window.$ = jQuery;
    }
    // 在非全局情况下 Jquery  $ 都有jq原型
    

    举个栗子

    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>jQuery.noConflict demo</title>
      <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
    </head>
    <body>
     
    <div id="log">
      <h3>Before $.noConflict(true)</h3>
    </div>
    <script src="https://code.jquery.com/jquery-1.6.2.js"></script>
     
    <script>
    var $log = $( "#log" );
     
    $log.append( "2nd loaded jQuery version ($): " + $.fn.jquery + "<br>" );
     
    // Restore globally scoped jQuery variables to the first version loaded
    // (the newer version)
     
    jq162 = jQuery.noConflict( true );
     
    $log.append( "<h3>After $.noConflict(true)</h3>" );
    $log.append( "1st loaded jQuery version ($): " + $.fn.jquery + "<br>" );
    $log.append( "2nd loaded jQuery version (jq162): " + jq162.fn.jquery + "<br>" );
    </script>
     
    </body>
    </html>
    Before $.noConflict(true)
    
    2nd loaded jQuery version ($): 1.6.2
    After $.noConflict(true)
    
    1st loaded jQuery version ($): 1.10.2
    2nd loaded jQuery version (jq162): 1.6.2
    

    今天的内容就到这里,下回我们讲解

    相关文章

      网友评论

        本文标题:Jquery源码解析(一)

        本文链接:https://www.haomeiwen.com/subject/vvnkkxtx.html