jQuery 1.8源码分析 core.js核心模块 jQuery对象的构造分析

1、init方法详解:

根据上一篇文章的分析,知道init方法其实就是在使用$() 创建jQuery对象是调用的,简单的说其实就是一个元素选择器,在API文档中可以发现,该方法可以提供如下几个用法:

jQuery([selector,[context]])

概述

这个函数接收一个包含 CSS 选择器的字符串,然后用这个字符串去匹配一组元素。

jQuery 的核心功能都是通过这个函数实现的。 jQuery中的一切都基于这个函数,或者说都是在以某种方式使用这个函数。这个函数最基本的用法就是向它传递一个表达式(通常由 CSS 选择器组成),然后根据这个表达式来查找所有匹配的元素。 默认情况下, 如果没有指定context参数,$()将在当前的 HTML document中查找 DOM 元素;如果指定了 context 参数,如一个 DOM 元素集或 jQuery 对象,那就会在这个 context 中查找。在jQuery 1.3.2以后,其返回的元素顺序等同于在context中出现的先后顺序。 参考文档中 选择器 部分获取更多用于 expression 参数的 CSS 语法的信息。

jQuery(html,[ownerDocument]) 返回值:jQuery

概述

根据提供的原始 HTML 标记字符串,动态创建由 jQuery 对象包装的 DOM 元素。同时设置一系列的属性、事件等。

你可以传递一个手写的 HTML 字符串,或者由某些模板引擎或插件创建的字符串,也可以是通过 AJAX 加载过来的字符串。但是在你创建 input 元素的时会有限制,可以参考第二个示例。当然这个字符串可以包含斜杠 (比如一个图像地址),还有反斜杠。当你创建单个元素时,请使用闭合标签或 XHTML 格式。例如,创建一个 span ,可以用$(““) 或 $(““) ,但不推荐 $(““)。在jQuery 中,这个语法等同于$(document.createElement(“span”)) 。 在jQuery 1.8中,通过$(html,props), 您可以使用任何jQuery对象的方法或插件。在此之前,你只能使用一个方法名的短名单,并有没有成文的方式添加到列表中。现在并不需要是一个列表,在所有!然而,请注意,这可能会导致你的代码的行为改变,如果插件添加后,有相同的名称作为HTML属性。

jQuery(callback) 返回值:jQuery

概述

$(document).ready()的简写。

允许你绑定一个在DOM文档载入完成后执行的函数。这个函数的作用如同$(document).ready()一样,只不过用这个函数时,需要把页面中所有需要在 DOM 加载完成时执行的$()操作符都包装到其中来。从技术上来说,这个函数是可链接的--但真正以这种方式链接的情况并不多。 你可以在一个页面中使用任意多个$(document).ready事件。参考 ready(Function) 获取更多 ready 事件的信息。

为了阅读这个方法,因为里面用到了一个在core.js变量定义部分定义的正则表达式 rquickExpr,所以我们首先来分析一下这个正则表达式的作用。

1.1、init方法中使用的正则表达式:

rquickExpr = /^(?:(<[\w\W]+>)[^>]|#([\w-]))$/,

这里有两个需要强调的正则,如下,其他的自己可以从API文档中找到对应的解释:

(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。

x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。

分析可以知道,这个正则表达式匹配(<[\w\W]+>)[^>]或者#([\w-]))$。

首先看到 (<[\w\W]+>)[^>]* :贪婪模式,最大范围的匹配,例如匹配 > >>asf12,就是从第一个”<”开始,知道最后一个”>”,还有后面的非”>”字符。

([\w-]*):匹配jQuery中的一个ID,如 #abc

所以这个正则的作用就是匹配传入参数中包含的HTML代码或者元素ID。

1.2、关于该方法编码涉及的一点技巧:

虽然是一个init方法,但是通过各种判断(可以查看代码中HANDLE提示,表示处理了不同的输入请求)以及JS的动态参数特性,为jQuery提供了多种初始化方法。还有一个就是获取到的dom节点的信息都封装到了this数组中,方便循环获取。

1.3、下面是对这个方法代码的解释:

// jQuery对象的初始化方法,实际上就是一个选择器
init: function( selector, context, rootjQuery ) {
var match, elem;

// HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
    // 传入了空,null,undefined或者false的时候,直接返回this引用,没有包含任何节点
    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
        // 如果是由"<"和">"包裹的HTML则跳过正则匹配,此时是根据传入的HTML创建一个Dom节点
        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\] ) {
            // 根据传入的HTML创建一个Dom节点

            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)
            // 根据传入的 HTML创建Dom节点,并根据传入的context设置prop属性
            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的情况
        } else {
            // 根据匹配到的ID使用JS的 getElementById() 方法进行获取元素
            elem = document.getElementById( match\[2\] );

            // Check parentNode to catch when Blackberry 4.6 returns
            // nodes that are no longer in the document #6963
            // 6963 bug 处理:处理Blackberry 4.6中返回了一个父节点已经不存在于document中的节点的情况
            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;
        }

    // 下面两个if语句都调用了jQuery的 find() 方法,详细实现参考 traversing.js
    // 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)
// 传入的是节点类型,直接保存到this数组中返回
} else if ( selector.nodeType ) {
    this.context = this\[0\] = selector;
    this.length = 1;
    return this;

// HANDLE: $(function)
// Shortcut for document ready  相当于 $(document).ready() 的缩写
// 传入的是函数,则通过rootjQuery.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 );

},

分析代码可知,init方法做了很多个API的实现,比如传入HTML代码创建节点,或者根据id在全局(jQueryRoot)或者指定的范围Context中进行查找。而在指定范围的情况下,调用了jQuery的find() 方法,下面再打开traversing.js中的该方法分析一下:

当我们给$符传递进一个参数(也可能是多个)时,此时它会根据参数的类型(domElement | string | fn | array)进入不同的流程。首先调用正则匹配看是否为创建dom节点的操作,然后看是否为简单id匹配,这一步也由正则匹配完成,否则进入jQuery.fn.find()函数,我们可以打开traversing.js中的该方法分析一下:

find: function( selector ) {
var i, ret, self;
// 如果传入的不是字符串,即可能是Dom节点或者jQuery对象
if ( typeof selector !== “string” ) {
self = this;
// 把过滤获取到的匹配元素数组push到堆栈中,并返回这个元素数组
return this.pushStack( jQuery( selector ).filter(function() {
for ( i = 0; i < self.length; i++ ) {
if ( jQuery.contains( self[ i ], this ) ) {
return true;
}
}
}) );
}

// 下面是传入的参数selector为字符串的情况
ret = \[\];
for ( i = 0; i < this.length; i++ ) {
    // 这里调用了jQuery.find方法,详细查看 sizzle-jquery.js文件,定义如下 jQuery.find = Sizzle;
    // 可见其实现在Sizzle中,可以获取其源代码详细阅读: https://github.com/jquery/sizzle
    jQuery.find( selector, this\[ i \], ret );
}

// Needed because $( selector, context ) becomes $( context ).find( selector )
ret = this.pushStack( jQuery.unique( ret ) );
ret.selector = ( this.selector ? this.selector + " " : "" ) \+ selector;
return ret;

},

其中pushStack()函数的作用是创建一个对象数组,把传入的对象数组也合并进去然后添加到一个栈中,并返回该新建的对象数组:

// Take an array of elements and push it onto the stack
// (returning the new matched element set)
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;
ret.context = this.context;

// Return the newly-formed element set
return ret;

},

我们可以看到里面都使用了Sizzle去进行搜索元素,为了探个究竟,我们可以在jQuery的官网http://api.jquery.com/顶部找到Sizzle项目的链接http://sizzlejs.com/ 正如其介绍的,Sizzle是一个纯JavaScript实现的CSS选择器引擎可以方便的嵌入到各种其他的JS类库中用来充当选择器。github上的源代码码下载:https://github.com/jquery/sizzle 这里我们就先跳过这个,看看jQuery的其他模块的,了解整体的架构。

arthinking wechat
欢迎关注itzhai公众号