jQuery源码学习之$()

450 阅读2分钟

$选择器

Dom对象和jquery对象

var box1 = document.getElementById('box');
var box2 = document.querySelector('#box');
var $box = $('#box');
console.log(box1);
console.log(box2);
console.log($box);

$()把DOM对象封装成jquery对象,而DOM对象也就保存在jquery[0]中,这也就是为什么我们说的把jquery对象转化成DOM对象只需用jquery[0]或者jquery.get(0)了。

首先,jQuery源码的整体构架如下:

$()的定义


jQuery()在src/core.js中定义,若在该方法中调用return new jQuery()则陷入循环,所以调用init()协助构造实例。值得一提的是,jQuery.fn在/src/core.js指向了jQuery.prototype。
// Define a local copy of jQuery
jQuery = function( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    // Need init if jQuery is called (just allow error to be thrown if not included)
    return new jQuery.fn.init( selector, context );
}

init方法的定义


jQuery.fn.init()在src/core/init.js中定义。方法接受三个参数selector,context, root,在方法内部,先判断是否有参数,无参数时返回false。
init = jQuery.fn.init = function( selector, context, root ) {
    var match, elem;

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

    // Method init() accepts an alternate rootjQuery
    // so migrate can support jQuery.sub (gh-2101)
    root = root || rootjQuery;

    // Handle HTML strings
    // < xxx > 或 $(#id)
    if ( typeof selector === "string" ) {
        if ( selector[ 0 ] === "<" &&
            selector[ 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[1]是html字符串,match[2]是匹配元素的id
            // selector是id选择器时match[1]为undefined,match[2]是匹配元素的id
            // selector是html字符串,match[1]是html字符串,match[2]为undefined
            match = rquickExpr.exec( selector );
        }

        // Match html or make sure no context is specified for #id
        // 匹配结果非空 且 存在匹配字符串或context空时执行
        // 未为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
                // 生成dom节点并合并到this上
                jQuery.merge( this, jQuery.parseHTML(
                    match[ 1 ],
                    context && context.nodeType ? context.ownerDocument || context : document,
                    true
                ) );

                // HANDLE: $(html, props)
                // 遍历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)
            // 处理id选择器且无context
            } 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, $(...))
        // selector是选择器 context为undefined或context.jquery存在时执行。
        // $(#id,context)或$(.class [, context])等情况
        } else if ( !context || context.jquery ) {
            return ( context || root ).find( selector );

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

    // HANDLE: $(DOMElement)
    // 传入DOM元素
    } else if ( selector.nodeType ) {
        this[ 0 ] = selector;
        this.length = 1;
        return this;

    // HANDLE: $(function)
    // Shortcut for document ready
    } else if ( jQuery.isFunction( selector ) ) {
        return root.ready !== undefined ?
            root.ready( selector ) :

            // Execute immediately if ready is not present
            selector( jQuery );
    }

    return jQuery.makeArray( selector, this );
};
jQuery.extend( {
···
    makeArray: function( arr, results ) {
        var ret = results || [];
        if ( arr != null ) {
            if ( isArrayLike( Object( arr ) ) ) {
                jQuery.merge( ret,
                    typeof arr === "string" ?
                    [ arr ] : arr
                );
            } else {
                push.call( ret, arr );
            }
        }
        return ret;
    },
    // Support: Android <=4.0 only, PhantomJS 1 only
    // push.apply(_, arraylike) throws on ancient WebKit
    merge: function( first, second ) {
        var len = +second.length,
            j = 0,
            i = first.length;
        for ( ; j < len; j++ ) {
            first[ i++ ] = second[ j ];
        }
        first.length = i;
        return first;
    },
···
} );

selector是字符串


- 如果有selector非空,先处理selector是字符串的情况,分为html字符串、$(selector)、$(expr, $(...))和$(expr,context)四种。如果selector是字符串类型,根据传入的字符串返回生成的dom节点,处理时先用正则匹配,查找html字符串或id。匹配结果非空且存在匹配字符串或context空时说明selctor是html字符串或selector是id选择器且未限定查找上下文。执行处理html字符串时,先确定生成后的节点要插入的document是哪个(即context参数),默认是加载jQuery的document,调用$.parseHTML()生成dom节点并添加到this;
  • 如果context是对象,则是$(html, props)的调用,将属性或者方法挂载到dom上,返回生成的jq对象。如果匹配到$(#id)的调用且context空时,则直接调用document.getElementById查找元素,元素存在时将this[0]指向该元素,返回查找结果。

  • 如果selector不是id选择器或context非空,调用find进行查找,如果context非空,则从context开始查找,否则全局查找,将查找结果作为返回值。

selector是DOM元素


接着处理传入参数是Dom元素的情况。将this[0]指向Dom元素,设置jq对象长度为1,并返回this。

selector是函数


最后处理$(function(){}),如果存在ready则调用传入函数调用ready(f()),否则传入jQuery,直接调用函数,调用makeArray,将其结果作为返回值。

修改init的原型


```js init = jQuery.fn.init = function( selector, context, root ) { ... } // Give the init function the jQuery prototype for later instantiation init.prototype = jQuery.fn; ``` 在原型上定义方法init,然后将init的原型指向jQuery的原型,如果不这么做,则创建的实例的原型是init.prototype,不是jQuery.fn,其实是init的实例而不是jQuery的实例,无法调用在core.js中定义在jQuery.fn上的各种变量和方法。