美文网首页
Jquery源码解析(二)

Jquery源码解析(二)

作者: 我是上帝可爱多 | 来源:发表于2017-07-27 15:58 被阅读38次

    今天我们带来jquery源码分析第二期,可能是最后一期哟,具体看以后的发现吧,话不多说直接上代码。

    1.节点查询

    先看一个函数

    jquery.extend({
    map: function( elems, callback, arg ) {
            var length, value,
                i = 0,
                ret = [];
                    //判断是否是类数组对象,dom节点是一种类数组对象
            if ( isArrayLike( elems ) ) {
                length = elems.length;
                for ( ; i < length; i++ ) {
                    value = callback( elems[ i ], i, arg );
    
                    if ( value != null ) {
                        ret.push( value );
                    }
                }
    
            } else {
                for ( i in elems ) {
                    value = callback( elems[ i ], i, arg );
    
                    if ( value != null ) {
                        ret.push( value );
                    }
                }
            }
                    //把数组扁平化
            return concat.apply( [], ret );
        }
    })
    数组扁平化:
    let aa = [[1,2,3,4],[2,3,4,5]]
    let bb = [].concat(aa)
    bb: [1,2,3,4,2,3,4,5]
    

    前面是预热,接下来看这个好吧

    jQuery.each( {
        parent: function( elem ) {
            var parent = elem.parentNode;
            return parent && parent.nodeType !== 11 ? parent : null;
        },
        parents: function( elem ) {
            return dir( elem, "parentNode" );
        },
        parentsUntil: function( elem, i, until ) {
            return dir( elem, "parentNode", until );
        },
        next: function( elem ) {
            return sibling( elem, "nextSibling" );
        },
        prev: function( elem ) {
            return sibling( elem, "previousSibling" );
        },
        nextAll: function( elem ) {
            return dir( elem, "nextSibling" );
        },
        nextUntil: function( elem, i, until ) {
            return dir( elem, "nextSibling", until );
        },
        siblings: function( elem ) {
            return siblings( ( elem.parentNode || {} ).firstChild, elem );
        },
        children: function( elem ) {
            return siblings( elem.firstChild );
        }
     }
    
            // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
            // Treat the template element as a regular one in browsers that
            // don't support it.
            if ( nodeName( elem, "template" ) ) {
                elem = elem.content || elem;
            }
    
            return jQuery.merge( [], elem.childNodes );
        }
    }, function( name, fn ) {
        jQuery.fn[ name ] = function( until, selector ) {
            var matched = jQuery.map( this, fn, until );
    
            if ( name.slice( -5 ) !== "Until" ) {
                selector = until;
            }
    
            if ( selector && typeof selector === "string" ) {
                matched = jQuery.filter( selector, matched );
            }
    
            if ( this.length > 1 ) {
    
                // Remove duplicates
                if ( !guaranteedUnique[ name ] ) {
                    jQuery.uniqueSort( matched );
                }
    
                // Reverse order for parents* and prev-derivatives
                if ( rparentsprev.test( name ) ) {
                    matched.reverse();
                }
            }
    
            return this.pushStack( matched );
        };
    } );
    
    

    以上代码是什么意思呢,我来稀释一下。。。

    jQuery.fn.parents = function(utill,selector){}
    jQuery.fn.parentsUtil = function(utill,selector){}
    jQuery.fn.nextUtil = function(utill,selector){}
    等等
    
    var matched = jQuery.map( this, fn, until );
    这个时候用了map函数,假设此时我们调用$('#qwe').nextUtil('.aa','.bb'),那么情况发展如下:
    1. jQuery.map($('#qwe'),nextUtil,'.aa')
    2. $('#qwe')是类数组元素,只有一个。会执行nextUtil($('#qwe'),0,'.aa')
    3.nextUtil返回的是 dir($('#qwe'),nextSbling,'.aa')
    4.我们来看看dir函数
    
    var dir = function( elem, dir, until ) {
        var matched = [],
            truncate = until !== undefined;
            // 这里是是查找#qwe的同辈 .aa节点
        while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
            if ( elem.nodeType === 1 ) {
                // 如果碰到了util也就是元素自身,就break
                if ( truncate && jQuery( elem ).is( until ) ) {
                    break;
                }
                matched.push( elem );
            }
        }
        return matched;
    };
    注意上面是找出了所有#qwe的同辈.aa元素,但我们想在他碰到.bb就停止,所以有这样一段代码:
    if ( selector && typeof selector === "string" ) {
                matched = jQuery.filter( selector, matched );
            }
    
    
    我们上面是特殊例子,若是$('.qwe')在页面中有多个,则会走完整个for循环。
    
    

    我么来看看 jQuery.filter( selector, matched )是如何写的

    jQuery.filter = function( expr, elems, not ) {
        var elem = elems[ 0 ];
        // 过滤不符合expr的
        if ( not ) {
            expr = ":not(" + expr + ")";
        }
    
        if ( elems.length === 1 && elem.nodeType === 1 ) {
            return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
        }
    
        return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
            return elem.nodeType === 1;
        } ) );
    };
    
    grep: function( elems, callback, invert ) {
            var callbackInverse,
                matches = [],
                i = 0,
                length = elems.length,
                callbackExpect = !invert;
    
            for ( ; i < length; i++ ) {
                callbackInverse = !callback( elems[ i ], i );
                if ( callbackInverse !== callbackExpect ) {
                    matches.push( elems[ i ] );
                }
            }
    
            return matches;
        }
    
    grep是一个过滤函数,invert如果为false,则与callback返回的之相反的元素进去数组。
    jQuery.find.matches大家可以到源码去查看一下,这里不具体讲了。
    

    我们来看看jquery里面很重要的一个函数pushStack

    pushStack: function( elems ) {
    
            // Build a new jQuery matched element set
            var ret = jQuery.merge( this.constructor(), elems );
    
            // Add the old object onto the stack (as a reference)
            ret.prevObject = this;
    
            // Return the newly-formed element set
            return ret;
        }
    我们发现在上面任意一个节点查询的函数最后都会有一个,this.pushStack(matched),把查找出来的元素放进去。
    ---html代码---
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div class="aa"></div>
    <div class="bb"></div>
    <div class="bb ww"></div>
    <div class="bb"></div>
    <div class="cc"></div>
    
    <script src="jquery-3.2.1.js"></script>
    <script>
    
    console.log($('.aa').nextUntil('.cc'))
    </script>
    </body>
    </html>
    
    2017-07-27 11-54-08屏幕截图.png

    相信大家看到了打印结果就很明白了,我们做完节点操作后都会的到一个新的dom对象,我们想回到之前操作的元素可以怎么办呢?

    考虑下面代码:
    $('.aa').nextUntil('.cc').find('.ww') 此时我们dom对象停在了$('.ww'),如果我们想回到$('.aa')怎么办
    
    end: function() {
            return this.prevObject || this.constructor();
        }, 
    源码里面有这样一段,返回上次操作的dom元素,使用end方法。
    
    $.fn.grandparent = function() { 
    var els = this.parent().parent(); 
    return this.pushStack(els.get()); 
    }; 
    $('.child').grandparent().end()  进行了2次查找,但一次end就可以返回最初元素。
    

    关于节点我们再来介绍一个addback方法,结果显然易见。

    <html>
    <head>
      <style>
    p, div { margin:5px; padding:5px; }
    .border { border: 2px solid red; }
    .background { background:yellow; }
    .left, .right { width: 45%; float: left;}
    .right { margin-left:3%; }
        </style>
      <script src="http://code.jquery.com/jquery-latest.js"></script>
    </head>
    <body>
     
    <div class="left">
      <p><strong>Before <code>addBack()</code></strong></p>
      <div class="before-addback">
        <p>First Paragraph</p>
        <p>Second Paragraph</p>
      </div>
    </div>
    <div class="right">
      <p><strong>After <code>addBack()</code></strong></p>
      <div class="after-addback">
        <p>First Paragraph</p>
        <p>Second Paragraph</p>
      </div>
    </div>
     
    <script>
    $("div.left, div.right").find("div, div > p").addClass("border");
     
    // First Example
    $("div.before-addback").find("p").addClass("background");
     
    // Second Example
    $("div.after-addback").find("p").addBack().addClass("background");
    </script>
     
    </body>
    </html>
    

    2. 元素操作

    相信下面一段代码大家会经常用到,控制显示和隐藏

    jQuery.fn.extend( {
        show: function() {
            return showHide( this, true );
        },
        hide: function() {
            return showHide( this );
        },
        toggle: function( state ) {
            if ( typeof state === "boolean" ) {
                return state ? this.show() : this.hide();
            }
    
            return this.each( function() {
                if ( isHiddenWithinTree( this ) ) {
                    jQuery( this ).show();
                } else {
                    jQuery( this ).hide();
                }
            } );
        }
    } );
    

    原本简单的功能其实在源码里面也有复杂的判断,来看showHide函数

    function showHide( elements, show ) {
        var display, elem,
            values = [],
            index = 0,
            length = elements.length;
    
        // Determine new display value for elements that need to change
        for ( ; index < length; index++ ) {
            elem = elements[ index ];
            if ( !elem.style ) {
                continue;
            }
    
            display = elem.style.display;
            if ( show ) {
    
                // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
                // check is required in this first loop unless we have a nonempty display value (either
                // inline or about-to-be-restored)
                if ( display === "none" ) {
                    values[ index ] = dataPriv.get( elem, "display" ) || null;
                    if ( !values[ index ] ) {
                        elem.style.display = "";
                    }
                }
                if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
                    values[ index ] = getDefaultDisplay( elem );
                }
            } else {
                if ( display !== "none" ) {
                    values[ index ] = "none";
    
                    // Remember what we're overwriting
                    dataPriv.set( elem, "display", display );
                }
            }
        }
    
        // Set the display of the elements in a second loop to avoid constant reflow
        for ( index = 0; index < length; index++ ) {
            if ( values[ index ] != null ) {
                elements[ index ].style.display = values[ index ];
            }
        }
    
        return elements;
    }
    showHide如果跟的参数是true,就代表是显示,dataPriv.set( elem, "display", display )是用来干嘛的呢,记录元素上一次的display属性
    

    有一个data的原型函数如下:

    function Data() {
        this.expando = jQuery.expando + Data.uid++;
    }
    
    Data.uid = 1;
    
    Data.prototype = {
    
        cache: function( owner ) {
    
            
            var value = owner[ this.expando ];
    
            if ( !value ) {
                value = {};
                    if ( acceptData( owner ) ) {
    
                    if ( owner.nodeType ) {
                        owner[ this.expando ] = value;
    
                        } else {
                        Object.defineProperty( owner, this.expando, {
                            value: value,
                            configurable: true
                        } );
                    }
                }
            }
                   return value;
        },
        set: function( owner, data, value ) {
            var prop,
                cache = this.cache( owner );
    
            // Handle: [ owner, key, value ] args
            // Always use camelCase key (gh-2257)
            if ( typeof data === "string" ) {
                cache[ jQuery.camelCase( data ) ] = value;
    
            // Handle: [ owner, { properties } ] args
            } else {
    
                // Copy the properties one-by-one to the cache object
                for ( prop in data ) {
                    cache[ jQuery.camelCase( prop ) ] = data[ prop ];
                }
            }
            return cache;
        },
        get: function( owner, key ) {
            return key === undefined ?
                this.cache( owner ) :
    
                owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
        }
    

    其实就是一段数据储存功能的函数,都有cache而已。咱们接着往下看

    jQuery.fn.extend( {
        detach: function( selector ) {
            return remove( this, selector, true );
        },
    
        remove: function( selector ) {
            return remove( this, selector );
        },
    
        text: function( value ) {
            return access( this, function( value ) {
                return value === undefined ?
                    jQuery.text( this ) :
                    this.empty().each( function() {
                        if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
                            this.textContent = value;
                        }
                    } );
            }, null, value, arguments.length );
        },
    
        append: function() {
            return domManip( this, arguments, function( elem ) {
                if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
                    var target = manipulationTarget( this, elem );
                    target.appendChild( elem );
                }
            } );
        },
    
        prepend: function() {
            return domManip( this, arguments, function( elem ) {
                if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
                    var target = manipulationTarget( this, elem );
                    target.insertBefore( elem, target.firstChild );
                }
            } );
        },
    
        before: function() {
            return domManip( this, arguments, function( elem ) {
                if ( this.parentNode ) {
                    this.parentNode.insertBefore( elem, this );
                }
            } );
        },
    
        after: function() {
            return domManip( this, arguments, function( elem ) {
                if ( this.parentNode ) {
                    this.parentNode.insertBefore( elem, this.nextSibling );
                }
            } );
        },
    
        empty: function() {
            var elem,
                i = 0;
    
            for ( ; ( elem = this[ i ] ) != null; i++ ) {
                if ( elem.nodeType === 1 ) {
    
                    // Prevent memory leaks
                    jQuery.cleanData( getAll( elem, false ) );
    
                    // Remove any remaining nodes
                    elem.textContent = "";
                }
            }
    
            return this;
        },
    

    我们看到了一堆耳熟能详的jquery方法,我们来看看是如何定义的。

    detach: function( selector ) {
            return remove( this, selector, true );
        },
    删除一个元素并将他的返回值保留,以后可能会再加上去。
    
    function remove( elem, selector, keepData ) {
        var node,
            nodes = selector ? jQuery.filter( selector, elem ) : elem,
            i = 0;
    
        for ( ; ( node = nodes[ i ] ) != null; i++ ) {
            if ( !keepData && node.nodeType === 1 ) {
                jQuery.cleanData( getAll( node ) );
            }
    
            if ( node.parentNode ) {
                if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
                    setGlobalEval( getAll( node, "script" ) );
                }
                node.parentNode.removeChild( node );
            }
        }
    
        return elem;
    }
    
    detach() 会保留所有绑定的事件、附加的数据,这一点与 remove() 不同。
    
    

    其实无论remove还是detach都能够保留原始元素,如果是remove会调用cleanData方法,来看一下吧

    cleanData: function( elems ) {
            var data, elem, type,
                special = jQuery.event.special,
                i = 0;
    
            for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
                if ( acceptData( elem ) ) {
                    if ( ( data = elem[ dataPriv.expando ] ) ) {
                        if ( data.events ) {
                            for ( type in data.events ) {
                                if ( special[ type ] ) {
                                    jQuery.event.remove( elem, type );
    
                                // This is a shortcut to avoid jQuery.event.remove's overhead
                                } else {
                                    jQuery.removeEvent( elem, type, data.handle );
                                }
                            }
                        }
    
                    
                        elem[ dataPriv.expando ] = undefined;
                    }
                    if ( elem[ dataUser.expando ] ) {
    
                        // Support: Chrome <=35 - 45+
                        // Assign undefined instead of using delete, see Data#remove
                        elem[ dataUser.expando ] = undefined;
                    }
                }
            }
        }
    我们可以看到cleanData把元素的data和event都给清除了
    

    3. 其他技巧

    jQuery.fn.extend( {
        hover: function( fnOver, fnOut ) {
            return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
        }
    } );
    
    

    我们用jquery写一个鼠标上去个鼠标下来事件,上面一个函数很轻松就解决了。

    trigger: function( type, data ) {
            return this.each( function() {
                jQuery.event.trigger( type, data, this );
            } );
        }
    我们在个一个元素绑定事件后,会用trigger来主动触发这个绑定的事件,还可以给他传递一些参数。
    
    

    下面就会看到我们经常用的动画函数了

    jQuery.fn.extend( {
        fadeTo: function( speed, to, easing, callback ) {
              return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
                  .end().animate( { opacity: to }, speed, easing, callback );
        },
        animate: function( prop, speed, easing, callback ) {
            var empty = jQuery.isEmptyObject( prop ),
                optall = jQuery.speed( speed, easing, callback ),
                doAnimation = function() {
    
                    // Operate on a copy of prop so per-property easing won't be lost
                    var anim = Animation( this, jQuery.extend( {}, prop ), optall );
    
                    // Empty animations, or finishing resolves immediately
                    if ( empty || dataPriv.get( this, "finish" ) ) {
                        anim.stop( true );
                    }
                };
                doAnimation.finish = doAnimation;
    
            return empty || optall.queue === false ?
                this.each( doAnimation ) :
                this.queue( optall.queue, doAnimation );
        }
    

    Jquery.speed是一个控制动画速度的函数,来看看如何定义

    jQuery.speed = function( speed, easing, fn ) {
        var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
            complete: fn || !fn && easing ||
                jQuery.isFunction( speed ) && speed,
            duration: speed,
            easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
        };
    //注意这里飘逸的js语法,一般speed给的是数字,所以opt实际是一个运动对象。
    
        // Go to the end state if fx are off
        if ( jQuery.fx.off ) {
            opt.duration = 0;
    
        } else {
            if ( typeof opt.duration !== "number" ) {
                if ( opt.duration in jQuery.fx.speeds ) {
                    opt.duration = jQuery.fx.speeds[ opt.duration ];
    
                } else {
                    opt.duration = jQuery.fx.speeds._default;
                }
            }
        }
    
        if ( opt.queue == null || opt.queue === true ) {
            opt.queue = "fx";
        }
    
        // Queueing
        opt.old = opt.complete;
           //重新定义了complete函数,dequeue是干嘛的呢。
        opt.complete = function() {
            if ( jQuery.isFunction( opt.old ) ) {
                opt.old.call( this );
            }
    
            if ( opt.queue ) {
                jQuery.dequeue( this, opt.queue );
            }
        };
    
        return opt;
    };
    

    由于dequeue涉及队列比较复杂,自己可以去看源码,最后来看一个很重的函数。

    jQuery.fn.extend( {
        offset: function( options ) {
    
            // 如果options存在,则是设置他的offset
            if ( arguments.length ) {
                return options === undefined ?
                    this :
                    this.each( function( i ) {
                        jQuery.offset.setOffset( this, options, i );
                    } );
            }
    
            var doc, docElem, rect, win,
                elem = this[ 0 ];
    
            if ( !elem ) {
                return;
            }
    
            if ( !elem.getClientRects().length ) {
                return { top: 0, left: 0 };
            }
    
            rect = elem.getBoundingClientRect();
    
            doc = elem.ownerDocument;
            docElem = doc.documentElement;
            win = doc.defaultView;
    
            return {
                top: rect.top + win.pageYOffset - docElem.clientTop,
                left: rect.left + win.pageXOffset - docElem.clientLeft
            };
        },
    

    我们想获得某个元素距离窗口左边和上边的距离可以用这个方法,解释一下getBoundingClientRect

    rectObject = object.getBoundingClientRect();
    DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。


    好了,今天就先讲到这里了,以后有机会在继续把最后一期给做完吧。。。

    相关文章

      网友评论

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

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