`

jQuery.API源码深入剖析以及应用实现(1) - 核心函数篇

阅读更多
Jquery(http://jquery.com/)是一个轻量级,快速简洁的Javascript框架,它的容量小巧,简洁和简短的语法,容易记;用户能更方便地处理HTML DOM、Events、实现动画效果,并且提供Ajax的支持。目前最新版本为 jQuery 1.3.1(http://jqueryjs.googlecode.com/files/jquery-1.3.1.js),这系列文章将对该版本的源码进行阐述。

现在开始本系列的第一篇,Jquery核心函数,内容主要包括:



 

分析

1. 在Jquery的应用开发中,我们经常看到这样的代码:

$("div .container").css("display","none"); //将div节点下样式值为container的节点设置为不显示
var width = $("div .container").width(); //得到div节点下样式值为container的宽度
var html = $(document.getElementById("result")).html(); //得到id为result的节点的innerHTML值
$("#result", document.forms[0]).css("color", "red"); //将在第一个Form节点下id为result的字体颜色设置为红色
$("<div>hello,world</div>").appendTo("#result"); //将HTML字符串信息 内部追加到 id为result的节点末尾
那么$(...)里面的参数,Jquery API中是怎样辨认参数是表达式,id,HTML字符串,还是DOM元素呢?

现在我们来深入剖析Jquery源码。


2. 这里,我先来做个测试,我将Jquery API简化为这样的代码:

(function(){
    var window = this,
    
    jQuery = window.jQuery = window.$ = function(selector, context){
        return new jQuery.fn.init(selector, context);
    };
    
    jQuery.fn = jQuery.prototype = {
        init : function(selector, context) {
            alert(selector); //弹出警告框
        }
    };
})();
window.onload = function() {
    $("div .container"); //得到“div . container”
    $("#result"); //得到“#result”
    $("<div>hello,world</div>"); //得到“<div>hello,world</div>”
    $(document.getElementById("result")); //得到“[object]”
}
从这里我们可以得出,实际上$里面的参数(表达式字符串,ID字符串,HTML字符串,DOM对象),主要就是在init方法中各自实现它们自己的逻辑。

现在列出init方法的具体实现:

    init: function( selector, context ) {
        // Make sure that a selection was provided
        selector = selector || document;

        // Handle $(DOMElement)
        if ( selector.nodeType ) {
            this[0] = selector;
            this.length = 1;
            this.context = selector;
            return this;
        }
        // Handle HTML strings
        if ( typeof selector === "string" ) {
            // Are we dealing with HTML string or an ID?
            var match = quickExpr.exec( selector );

            // Verify a match, and that no context was specified for #id
            if ( match && (match[1] || !context) ) {

                // HANDLE: $(html) -> $(array)
                if ( match[1] )
                    selector = jQuery.clean( [ match[1] ], context );

                // HANDLE: $("#id")
                else {
                    var elem = document.getElementById( match[3] );

                    // Handle the case where IE and Opera return items
                    // by name instead of ID
                    if ( elem && elem.id != match[3] )
                        return jQuery().find( selector );

                    // Otherwise, we inject the element directly into the jQuery object
                    var ret = jQuery( elem || [] );
                    ret.context = document;
                    ret.selector = selector;
                    return ret;
                }

            // HANDLE: $(expr, [context])
            // (which is just equivalent to: $(content).find(expr)
            } else
                return jQuery( context ).find( selector );

        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) )
            return jQuery( document ).ready( selector );

        // Make sure that old selector state is passed along
        if ( selector.selector && selector.context ) {
            this.selector = selector.selector;
            this.context = selector.context;
        }

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

3. 现在分析 表达式,id,HTML字符串,DOM元素等等各自的实现:

1)形如 $(document.getElementById("result")) 【jQuery(elements)】DOM元素的实现,通过init方法中的以下代码:

 // Handle $(DOMElement)
  if ( selector.nodeType ) {
   this[0] = selector;
   this.length = 1;
   this.context = selector;
   return this;
  }
selector.nodeType判断当selector为元素节点时,将length置为1,并且赋值于context,实际上context作为init的第二个参数,它意味着它的上下文节点就是selector该点,返回它的$(...)对象。

2)形如 $("<div>hello,world</div>") 【jQuery(html,[ownerDocument])】HTML字符串的实现,通过init方法中的以下代码:

// 判断selector为字符串
if ( typeof selector === "string" ) {
    // quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/
    // 利用检查正则表达式HTML字符串还是元素ID字符串
    var match = quickExpr.exec( selector );
    
    if ( match && (match[1] || !context) ) {

        // 处理HTML字符串
        if ( match[1] )
            selector = jQuery.clean( [ match[1] ], context );

        // 处理形如$("#id")
        else {
            // 
        }
    
    } 
    // 处理 形如 $("div .container")的表达式字符串
    else
        // 

}
 // 处理 形如 $(function) , $(document).ready(function(){})的表示
else if ( jQuery.isFunction( selector ) ) {

}
// 
关键看到这样的一句代码,selector = jQuery.clean( [ match[1] ], context ); 继续查看clean都做了些什么:

Code
    clean: function( elems, context, fragment ) {
        context = context || document;

        // !context.createElement fails in IE with an error but returns typeof 'object'
        if ( typeof context.createElement === "undefined" )
            context = context.ownerDocument || context[0] && context[0].ownerDocument || document;

        // If a single string is passed in and it's a single tag
        // just do a createElement and skip the rest
        if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
            var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
            if ( match )
                return [ context.createElement( match[1] ) ];
        }

        var ret = [], scripts = [], div = context.createElement("div");

        jQuery.each(elems, function(i, elem){
            if ( typeof elem === "number" )
                elem += '';

            if ( !elem )
                return;

            // Convert html string into DOM nodes
            if ( typeof elem === "string" ) {
                // Fix "XHTML"-style tags in all browsers
                elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
                    return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
                        all :
                        front + "></" + tag + ">";
                });

                // Trim whitespace, otherwise indexOf won't work as expected
                var tags = jQuery.trim( elem ).toLowerCase();

                var wrap =
                    // option or optgroup
                    !tags.indexOf("<opt") &&
                    [ 1, "<select multiple='multiple'>", "</select>" ] ||

                    !tags.indexOf("<leg") &&
                    [ 1, "<fieldset>", "</fieldset>" ] ||

                    tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
                    [ 1, "<table>", "</table>" ] ||

                    !tags.indexOf("<tr") &&
                    [ 2, "<table><tbody>", "</tbody></table>" ] ||

                     // <thead> matched above
                    (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
                    [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||

                    !tags.indexOf("<col") &&
                    [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||

                    // IE can't serialize <link> and <script> tags normally
                    !jQuery.support.htmlSerialize &&
                    [ 1, "div<div>", "</div>" ] ||

                    [ 0, "", "" ];

                // Go to html and back, then peel off extra wrappers
                div.innerHTML = wrap[1] + elem + wrap[2];

                // Move to the right depth
                while ( wrap[0]-- )
                    div = div.lastChild;

                // Remove IE's autoinserted <tbody> from table fragments
                if ( !jQuery.support.tbody ) {

                    // String was a <table>, *may* have spurious <tbody>
                    var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
                        div.firstChild && div.firstChild.childNodes :

                        // String was a bare <thead> or <tfoot>
                        wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
                            div.childNodes :
                            [];

                    for ( var j = tbody.length - 1; j >= 0 ; --j )
                        if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
                            tbody[ j ].parentNode.removeChild( tbody[ j ] );

                    }

                // IE completely kills leading whitespace when innerHTML is used
                if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
                    div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
                
                elem = jQuery.makeArray( div.childNodes );
            }

            if ( elem.nodeType )
                ret.push( elem );
            else
                ret = jQuery.merge( ret, elem );

        });

        if ( fragment ) {
            for ( var i = 0; ret[i]; i++ ) {
                if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
                    scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
                } else {
                    if ( ret[i].nodeType === 1 )
                        ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
                    fragment.appendChild( ret[i] );
                }
            }
            
            return scripts;
        }

        return ret;
    }
这么长的一串代码 实际上最后访问的是一个ret为变量的数组,而数组中的元素变为以DOM元素的对象,而它的innerHTML正好就是刚才的HTML字符串。


3)形如 $("#result") 【jQuery(expression,[context])】ID字符串的实现,通过init方法中的以下代码:

// 处理形如$("#result")
else {
    // match[3]得到ID的值如:result
    var elem = document.getElementById( match[3] );

    if ( elem && elem.id != match[3] )
        return jQuery().find( selector );

    // 调用jQuery(elements)方式
    var ret = jQuery( elem || [] );
    // 默认上下文DOM为window.document
    ret.context = document;
    ret.selector = selector;
    return ret;
}
根据match[3]可以得到DOM对象elem,并且调用2)介绍的jQuery(elements),最后返回一个ret为变量的jquery对象。


4)形如 $("div .container") 【jQuery(expression,[context])】表达式字符串的实现,通过init方法中的以下代码:

// 处理 形如 $("div .container")的表达式字符串
else
  return jQuery( context ).find( selector );
关键看到这样的一句代码,jQuery().find( selector ); 继续查看find都做了些什么:

find: function( selector ) {
    // 当表达式不包含“,”符号时候
    if ( this.length === 1 && !/,/.test(selector) ) {
        var ret = this.pushStack( [], "find", selector );
        ret.length = 0;
        jQuery.find( selector, this[0], ret );
        return ret;
    } 
    // 当表达式包含“,”符号时候
    else {
        var elems = jQuery.map(this, function(elem){
            return jQuery.find( selector, elem );
        });

        return this.pushStack( /[^+>] [^+>]/.test( selector ) ?
            jQuery.unique( elems ) :
            elems, "find", selector );
    }
}
 

先看下表达式不包含“,”符号的时候,调用pushStack方法,方法为:

// 将一系列元素推入栈中
pushStack: function( elems, name, selector ) {

    var ret = jQuery( elems );

    // 将上个对象的引用推入栈中
    ret.prevObject = this;

    ret.context = this.context;

    // 关键字为find时,在原有selector的基础上,继续增加selector
    // 如 $("div").find("p") 意思就是 $("div p")
    if ( name === "find" )
        ret.selector = this.selector + (this.selector ? " " : "") + selector;
    else if ( name )
        ret.selector = this.selector + "." + name + "(" + selector + ")";

    // 返回最新的Jquery对象
    return ret;
}
注意这里看到 ret.prevObject = this;  这个方法在$(...).andSelf()和$(...).end()中调用,对于筛选或查找后的元素,返回前一次元素状态它是很有用的。

接着调用 jQuery.find( selector, this[0], ret ); ,首先我们看到有这样的几句代码:

jQuery.find = Sizzle;
jQuery.filter = Sizzle.filter;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;
// 
window.Sizzle = Sizzle;
jQuery.find方法转去调用全局的Sizzle对象了(实际上这里运用到了Javascript设计模式中的适配器模式,jquery.find实际上调用的是Sizzle的对象。关于Javascript适配器模式,我将接下来的Javascript乱弹设计模式系列文章中具体叙述),Sizzle对象定义为:

Code
var Sizzle = function(selector, context, results, seed) {
    results = results || [];
    context = context || document;

    if ( context.nodeType !== 1 && context.nodeType !== 9 )
        return [];
    
    if ( !selector || typeof selector !== "string" ) {
        return results;
    }

    var parts = [], m, set, checkSet, check, mode, extra, prune = true;
    
    // Reset the position of the chunker regexp (start from head)
    chunker.lastIndex = 0;
    
    while ( (m = chunker.exec(selector)) !== null ) {
        parts.push( m[1] );
        
        if ( m[2] ) {
            extra = RegExp.rightContext;
            break;
        }
    }

    if ( parts.length > 1 && origPOS.exec( selector ) ) {
        if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
            set = posProcess( parts[0] + parts[1], context );
        } else {
            set = Expr.relative[ parts[0] ] ?
                [ context ] :
                Sizzle( parts.shift(), context );

            while ( parts.length ) {
                selector = parts.shift();

                if ( Expr.relative[ selector ] )
                    selector += parts.shift();

                set = posProcess( selector, set );
            }
        }
    } else {
        var ret = seed ?
            { expr: parts.pop(), set: makeArray(seed) } :
            Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
        set = Sizzle.filter( ret.expr, ret.set );

        if ( parts.length > 0 ) {
            checkSet = makeArray(set);
        } else {
            prune = false;
        }

        while ( parts.length ) {
            var cur = parts.pop(), pop = cur;

            if ( !Expr.relative[ cur ] ) {
                cur = "";
            } else {
                pop = parts.pop();
            }

            if ( pop == null ) {
                pop = context;
            }

            Expr.relative[ cur ]( checkSet, pop, isXML(context) );
        }
    }

    if ( !checkSet ) {
        checkSet = set;
    }

    if ( !checkSet ) {
        throw "Syntax error, unrecognized expression: " + (cur || selector);
    }

    if ( toString.call(checkSet) === "[object Array]" ) {
        if ( !prune ) {
            results.push.apply( results, checkSet );
        } else if ( context.nodeType === 1 ) {
            for ( var i = 0; checkSet[i] != null; i++ ) {
                if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
                    results.push( set[i] );
                }
            }
        } else {
            for ( var i = 0; checkSet[i] != null; i++ ) {
                if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
                    results.push( set[i] );
                }
            }
        }
    } else {
        makeArray( checkSet, results );
    }

    if ( extra ) {
        Sizzle( extra, context, results, seed );
    }

    return results;
};
呵呵,好长的一段代码,实际最关键的一句代码:Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
对表达式字符串进行解析,最后返回一个jQuery对象,它就是 表达式字符串 最后想要的jQuery对象。




当表达式包含“,”符号的时候,查看这样的一句代码:return this.pushStack( /[^+>] [^+>]/.test( selector ) ? jQuery.unique( elems ) : elems, "find", selector );

它的意思是当表达式字符串包含“+”,“>”作为选择器(比如$("form > input") 或者 $("label + input") )的时候,将作为jQuery.map返回值的elems数组删除重复的元素,这个是有必要的。
最后也是返回一个jQuery对象,它就是 表达式字符串 最后想要的jQuery对象。


5)最后一点,形如 $(function) , $(document).ready(function(){})的表示,通过init方法中的以下代码来实现:

// 处理 形如 $(function) , $(document).ready(function(){})的表示
else if ( jQuery.isFunction( selector ) )
    return jQuery( document ).ready( selector );
 

如果判断selector是函数的话,将执行jQuery(document).ready(selector);
ready方法具体为:


ready: function(fn) {
    // 绑定事件监听
    bindReady();

    if ( jQuery.isReady )
        fn.call( document, jQuery );

    else
        jQuery.readyList.push( fn );

    return this;
}
bindReady方法将事件绑定在文档加载完毕之后,最后通过调用fn.call( document, jQuery );来激发事件的执行。

 

好了,jQuery的核心函数的原理机制就是这样的,下一篇我将谈下jQuery对象访问和数据缓存的原理机制。

谢谢大家阅读! 

转自:http://www.cnblogs.com/liping13599168/archive/2009/02/09/1386623.html


分享到:
评论

相关推荐

    jQuery.API源码深入剖析以及应用实现

    《jQuery API 源码深入剖析及应用实现》 jQuery 是一个广泛使用的JavaScript库,它极大地简化了HTML文档遍历、事件处理、动画和Ajax交互等任务。jQuery API 是其核心,提供了丰富的函数和方法,使得开发者能够高效...

    jquery-easyui-1.5.3源代码(未压缩)

    jQuery EasyUI 是一个基于 jQuery 的前端框架,它提供了丰富的组件和便捷的API,用于快速构建交互式的Web应用。EasyUI 1.5.3 版本的源代码是开发者深入理解其内部工作原理、定制功能以及优化性能的重要参考资料。...

    jQuery源码分析-03构造jQuery对象-源码结构和核心函数

    在本部分中,将深入剖析jQuery对象的构造过程,以及其源码结构和核心函数的详细分析。 ### 3. 构造jQuery对象 #### 3.1 源码结构 在分析jQuery对象构造之前,先来看一下总体的源码结构。jQuery的源码是通过一个...

    jquery1.2.6源码分析rar + API

    本篇文章将深入剖析jQuery 1.2.6版本的源码,同时结合其API文档,为读者揭示jQuery的核心机制和设计理念。 一、jQuery核心概念 1. 选择器:jQuery的核心之一是其强大的选择器功能,它基于CSS选择器并扩展了一些...

    jquery源码好不容易找到的与大家分享

    今天,我们有幸深入剖析这份来之不易的jQuery源码,从中汲取精华,提升我们的前端开发技能。 一、jQuery的核心概念 jQuery的核心理念是简化DOM操作,提供更加流畅的事件处理和动画效果。它的出现,使得JavaScript...

    interview-api-源码.rar

    《深入剖析面试必备API源码》 在编程领域,API(Application Programming Interface)是软件之间交互的桥梁,它定义了不同软件系统之间如何通信。掌握API的使用与设计是每个开发者,尤其是面试者必备的技能。...

    jquery:jquery.2.1.0源码分析-jquery source code

    本篇文章将深入探讨jQuery 2.1.0版本的源码,帮助读者理解其内部机制,提升JavaScript编程能力。 一、jQuery对象与选择器 jQuery的核心是它的选择器引擎Sizzle,它使得我们能够通过CSS选择器快速定位DOM元素。在...

    Jquery源码分析.rar

    通过对jQuery 1.2.6源码的深入分析,我们可以了解到JavaScript库的设计原则,以及如何通过优秀的API设计和优化技术提升用户体验。尽管现代前端框架如React和Vue已经取代了jQuery的部分角色,但其设计理念和最佳实践...

    锋利的jQuery源码

    《锋利的jQuery源码》是一本深入剖析jQuery核心源码的专业书籍,旨在帮助开发者深入了解这一广泛使用的JavaScript库。jQuery以其简洁的API和强大的功能深受前端开发者喜爱,其源码蕴含了丰富的编程思想和技术实践。...

    锋利的jQuery 源码

    《锋利的jQuery源码》是一本深入解析jQuery核心源码的专业书籍,旨在帮助开发者深入了解这一广泛使用的JavaScript库。jQuery以其简洁的语法、强大的功能和跨浏览器兼容性,深受前端开发者的喜爱。这本书通过详细剖析...

    jquery很全的例子源码,事件 ajax,表单应用

    本资源包含了一系列全面的jQuery例子源码,旨在深入剖析jQuery在事件处理、Ajax异步通信以及表单操作中的应用。接下来,我们将逐一探讨这些关键知识点。 一、jQuery事件处理 1. 事件绑定:jQuery提供`.on()`方法来...

    jquery经典实例源码

    本篇文章将深入探讨一个名为“jquery经典实例源码”的资源包,该资源包包含了一系列实用且炫酷的jQuery代码实例,旨在帮助初学者和有经验的开发者更好地理解和应用jQuery。 首先,jQuery的核心特性是它对DOM(文档...

    jquery 冯威 源码

    冯威先生对jQuery源码的深入剖析,为我们揭示了这个库背后的运行机制和设计理念,这对于提升JavaScript编程技能、理解DOM操作、事件处理以及动画效果的实现具有极高的学习价值。 jQuery的核心设计理念是“Write ...

    锋利jquery最新源码

    jQuery,作为一款广泛使用的JavaScript库,为开发者提供了简洁易用的API,使得操作DOM、处理事件、执行动画以及实现Ajax交互变得更加简单。"锋利jQuery最新源码"是对jQuery核心源码的深度剖析,旨在帮助开发者深入...

    jQuery源码分析系列_1.6

    本篇文章将围绕jQuery 1.6版本进行深入剖析,旨在帮助读者理解其内部机制,进一步提升前端技能。 #### 二、总体架构 jQuery的核心可以分为几个主要部分: 1. **基础结构**:包括初始化和一些基本的工具方法。 2. ...

    Jquery1.2.6源码分析

    本文将深入剖析jQuery 1.2.6版本的源码,揭示其背后的机制与设计理念,帮助开发者更好地理解和运用这个强大的工具。 一、jQuery核心概念 1. 链式调用:jQuery的一个显著特征是链式调用,这得益于其返回值的设计。...

    佟刚jQuery源码含资源及PPT

    8. 源码剖析:深入分析jQuery核心代码,理解其内部机制和优化技巧。 这份资源包对初学者和有一定经验的开发者来说都是宝贵的资料,它不仅能帮助你快速掌握jQuery的基本用法,还能引导你深入了解其底层原理,从而更...

    jquery 原文件

    本篇文章将深入剖析jQuery的核心概念,解析其源码,并探讨在实际项目中的应用场景。 一、jQuery的诞生与理念 jQuery由John Resig于2006年创建,其核心理念是“Write Less, Do More”。通过提供一套强大的API,...

    jQuery 源码分析

    本文将深入探讨jQuery的核心源码,解析其设计理念,帮助读者理解其背后的实现机制。 一、jQuery的诞生与核心理念 jQuery由John Resig创建,旨在解决当时JavaScript中DOM操作复杂、浏览器兼容性问题多等痛点。其...

Global site tag (gtag.js) - Google Analytics