zoukankan      html  css  js  c++  java
  • jQuery.API源码深入剖析以及应用实现(1) - 核心函数篇

    前言

    Jquery(http://jquery.com/)是一个轻量级,快速简洁的Javascript框架,它的容量小巧,简洁和简短的语法,容易记;用户能更方便地处理HTML DOMEvents、实现动画效果,并且提供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方法的具体实现:

        initfunction( 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都做了些什么:


        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 
    + 10].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,并且调用1)介绍的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(thisfunction(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对象定义为:


    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的核心函数的原理机制就是这样的
  • 相关阅读:
    无法往u盘里边复制超过4G的单个文件解决方法
    vue 自定义属性判断点击每个item 显示隐藏
    前端小组分享会之异步回调函数中的上下文
    git 的一些命令
    ES学习之promise
    mac找到占用端口号的进程ID
    meta link
    webpack学习笔记--安装
    css样式之 direction
    日常积累之JSON.stringify和JSON.parse substr
  • 原文地址:https://www.cnblogs.com/haoliansheng/p/1408729.html
Copyright © 2011-2022 走看看