Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jQuery源码-美元$的若干种使用方法 #5

Open
chyingp opened this issue May 31, 2013 · 0 comments
Open

jQuery源码-美元$的若干种使用方法 #5

chyingp opened this issue May 31, 2013 · 0 comments
Labels

Comments

@chyingp
Copy link
Owner

chyingp commented May 31, 2013

jQuery源码-美元$的若干种使用方法

学习jQuery源码,第一步是了解jQuery整体核心代码结构。第二步,当然就是了解无比强大无所不能的美元$。根据平常使用jQuery的经验,你会发现,几乎所有的语句都是以美元开头的,比如:

$(function(){
    console.log('dom ready 啦!!');
});

又比如:

$('#casper').addClass('handsome');

当然还有其他。。。翻开jQuery的源码你会发现,里面就一行代码:

jQuery = function( selector, context ) {
  // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context, rootjQuery );   //就是这货
},

于是,我们接下来的任务就是一探jQuery.fn.init的究竟:里面究竟是什么东东,能够让美元符号$如此强大以至于无处不在。

初探jQuery.fn.init

老规矩,打开编辑器,定位到jQuery.fn.init这个方法。如果是用sublime的话,可以试下ctrl+r,然后输入init,第一个出来的搜索结果就是。

相信很多童鞋跟我的第一反应是:oh my god!将近90行代码!不过还可以接受啦,工作中还见别人写过300++行的方法,想想90行也算不得可怕。

然而,当你再往下看,可能就会有种想死的心——怎么这么多if、else!

不卖关子之所以会有那么多if、else,是因为——$有将近10种用法,文章最开头列举的不过是最常见的两种用法而已。

以下为jQuery.fn.init的源码,瞄一眼感受下这代码的可怕就可以了,可以暂时忽略其中的实现细节,安心进入下一节。

init: function( selector, context, rootjQuery ) {
    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
                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 rootjQuery.ready( selector );
    }

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

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

$的n种用法

上面提到,$的用法有将近10种,也就是说,jQuery.fn.init这一个函数需要处理的情况有将近10种。那究竟都是哪些情况呢?如果想从它的源码直接看出来的话,那最好放弃。当然并不是说此路不通,只不过有更好的方法而已。

jQuery之所以这么受欢迎,其中一个原因是它的文档很齐全,这个时候果断可以去看它的API文档,请猛击这里

jQuery的API文档里面很详细地将各种情况都列了出来,可以看到,里面共列举了三大种、八小种情况。至于每种情况的作用、参数、返回值,可自行查看API说明,这里不赘述。

jQuery( selector [, context ] )
    jQuery( selector [, context ] )
    jQuery( element )
    jQuery( elementArray )
    jQuery( object )
    jQuery( jQuery object )
    jQuery()
jQuery( html [, ownerDocument ] )
    jQuery( html [, ownerDocument ] )
    jQuery( html, attributes )
jQuery( callback )
    jQuery( callback )

$的n种用法——更直观的例子

上面我们已经将$的n种用法非常详细地列举出来了,但这只是第一步,因为对着jQuery.fn.init错综复杂的逻辑分支,你有可能依旧手忙脚乱,不知如何下手。里面比较明显能够看出来的是下面这几种情况:

jQuery()
jQuery( element )
jQuery( callback )

除了上面这三种情况外,其他五种情况依旧无法在代码里直观地看出来。那么肿么办呢?其实我也没有特别好的方法,但可以将自己的经验分享一下,分两步:

  1. 把jQuery的API文档详细地过一遍,了解$的多种用法以及细节,可点击这里
  2. 断点调试,针对上面列出的几种情况,编写最简单的代码试例,然后断点进去看看都跑到了哪些逻辑分支

断点用例

好,于是我们开始编写用例,下面提到的用例都基于下面的html片段

<div id="id_container" class="container">
    <div class="header"></div>
</div>

直接上具体的用例。对于这些用例的具体分析会在下面再讲到。

//
$('#id_container')
$('.container')

//
$('.header', $('#id_container')[0])
$('.header', $('#id_container'))

//
$(document.getElementsByTagName('div'))
$(document.getElementsByTagName('div')[0])
$($('.header'))
$({name:'casper', age:25})

//
$('<div class="content"><span>casper</span></div>')
$('<div class="content"><span>casper</span></div>', document)
$('<div></div>', {'class':'content'})
$('<div/>', {'class':'content'})

//
$(function(){
    console.log('$(callback)');
});

jQuery()

灰常简单,直接返回this(jQuery对象)

init: function( selector, context, rootjQuery ) {
    var match, elem;

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

$(callback)

这个很简单,直接跑进下面这个分支然后就return了

else if ( jQuery.isFunction( selector ) ) {
            return rootjQuery.ready( selector );
        } 

jQuery( element )

同样很简单,跑到这个分支里去了

else if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;
        }

jQuery( elementArray )

这个比较费解,似乎前面所有的if、else都不符合,没错,其实只有下面这么句话

return jQuery.makeArray( selector, this );

jQuery( jQuery object )

跳过代码细节,先了解下面的背景知识,看下面的代码。对于$(selector)返回的jQuery对象,上面都会附加一个selector属性,作用不介绍。

$('.header').selector

于是乎,华丽丽地跑进下面这个分支,其实作用就是:创建一个jQuery对象,并将参数jQuery对象里的dom节点拷贝到新创建的jQuery对象里

if ( selector.selector !== undefined ) {
    this.selector = selector.selector;
    this.context = selector.context;
}
return jQuery.makeArray( selector, this );

jQuery( selector [, context ] )

这个的话,情况比较多,分开讲

jQuery('#casper')

init: function( selector, context, rootjQuery ) {
    //各种省略
        } else {    //先跑到这个分支里去鸟
            match = rquickExpr.exec( selector );  //这里,match==['#casper', undefined, 'casper']
        }
    //各种省略
            // 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, $(...))
        }     // 下面全部省略

jQuery('#casper', docuemnt)

//首先进入这个分支
match = rquickExpr.exec( selector );  //['#casper', undefined, 'casper']

//然后进入这个分支
} else {
                return this.constructor( context ).find( selector );
            }

jQuery('.header')

//先进入这个分支
match = rquickExpr.exec( selector );  // match==null

//然后进入这个分支~
else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).find( selector );

            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } 

jQuery('.header', document)

//首先进入这里
match = rquickExpr.exec( selector );  // match==null

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

jQuery('.header', $('#id_container'))

//首先进入这里
match = rquickExpr.exec( selector );  // match==null

//然后进入这里
// HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).find( selector );

jQuery(html, attribute)

具体例子jQuery('<div></div>', {style: 'background:red;'}),或者jQuery('<div/>', {style: 'background:red'})

//首先进入这里
        // 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 ];  //[null, '<div></div>', null]

            }
//然后进入这里
            // 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;  // context == {style:{background:red}}

                    //先把创建好的dom借点复制到this里
                    // scripts is true for back-compat
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );
                    //然后将{style: 'background:red'}等属性添加到创建好的dom节点上
// 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)
                }

jQuery('<div><span>casper</span></div>')

这个应该是我们经常用到的。。。

        //首先华丽丽进入这个分支
        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 ];

            }
            //然后进入这个分支
            // 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
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );
// 木有赋值
                    return this;

结束语

好了,万恶的美元$就先介绍到这里,第二部的源码详解本来还想把代码路径全部标出来,这样更方便观众围观。不过markdown给改变代码的字体颜色不知道咋整将就吧,且听下回分解

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant