1.入口结构

( function( global, factory ) {
        "use strict";
        if ( typeof module === "object" && typeof module.exports === "object" ) {
            module.exports = global.document ? factory( global, true ) :
                function( w ) {
                    if ( !w.document ) {
                        throw new Error( "jQuery requires a window with a document" );
                    }
                    return factory( w );
                };
        } else {
            factory( global );
        }
    } )( 
        typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
        //具体代码
    }

抽离结构如下:

( function() { })()

涉及到的知识

  • js解析函数时的规则
  • 函数定义和函数表达式
  • js闭包

js解析函数的规则

  • js解析器会在遇到function时将其认为是函数定义而非函数表达式

函数定义和函数表达式

  • 函数定义:function test(){ }
  • 函数表达式:let test = function(){ }

js闭包

  • 闭包:函数中的函数,本质是指作用域内的作用域
//闭包举例
function f(){
    var a = 2;
    function g(){
        console.log(a)
    };
    return g;
}
f()();

综合以上的内容,再来看一下刚才抽离出来的代码

(function(){ })()
  • 第一个括号有两个作用:

    1. 让js解析器把后面的function当作函数表达式而不是函数定义
    2. 形成一个作用域,类似在上面闭包例子中的f函数
  • 第二个括号

    1. 触发函数并传参

2.第二个括号内的参数有哪些?

(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { })
  • 第一个参数是判断环境,传入全局对象
  • 第二个参数是确定环境后具体执行的代码

3.第一个括号内的函数做了什么?

( function( global, factory ) {
    "use strict";
    //判断是不是在commonjs环境下,如果是就执行以下代码
    if ( typeof module === "object" && typeof module.exports === "object" ) {
        //判断是否支持global.document
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                //不支持global.document时报错
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                //报错后返回jquery(w)
                return factory( w );
            };
    } else {
        //windows环境下执行这个代码
        factory( global );
    }
} )

4.判断完环境后通过:factory( global );跳转到第二个括号的第二个参数内执行具体的内容

//整体的结构抽离如下
function( window, noGlobal ) {
    "use strict";
    //具体的jquery内部代码
    
    if ( !noGlobal ) {
        //在window下可以用以下方式调用
        window.jQuery = window.$ = jQuery;
    }
    return jQuery;
}

5.进入函数后,先定义了一些变量,函数和对象(可以跳过先看下面的内容)

//定义了一些变量和方法
var arr = [];

var document = window.document;

var getProto = Object.getPrototypeOf;

//数组方法简写

var slice = arr.slice;

var concat = arr.concat;

var push = arr.push;

var indexOf = arr.indexOf;

//对象方法简写
var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call( Object );

var support = {};

//定义函数
var isFunction = function isFunction( obj ) {
      return typeof obj === "function" && typeof obj.nodeType !== "number";
  };


var isWindow = function isWindow( obj ) {
        return obj != null && obj === obj.window;
    };

function DOMEval( code, doc, node ) {
    doc = doc || document;

    var i,
        script = doc.createElement( "script" );

    script.text = code;
    if ( node ) {
        for ( i in preservedScriptAttributes ) {
            if ( node[ i ] ) {
                script[ i ] = node[ i ];
            }
        }
    }
    doc.head.appendChild( script ).parentNode.removeChild( script );
}

function toType( obj ) {
    if ( obj == null ) {
        return obj + "";
    }

    // Support: Android <=2.3 only (functionish RegExp)
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[ toString.call( obj ) ] || "object" :
        typeof obj;
}

//定义对象
var preservedScriptAttributes = {
    type: true,
    src: true,
    noModule: true
};

6.定义完上面的内容后,jQuery内部进行new对象,使得简化使用操作

var version = "3.3.1",

    //在这里jquery通过new新生成了对象简化了使用时的操作
    jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context );
    },

7.new新对象时,jQuery.fn是什么?

jQuery.fn = jQuery.prototype = {
    jquery: version,
    constructor: jQuery,
    length: 0,
    toArray: function() {
        return slice.call( this );
    },
    get: function( num ) {
        if ( num == null ) {
            return slice.call( this );
        }
        return num < 0 ? this[ num + this.length ] : this[ num ];
    },
    pushStack: function( elems ) {
        var ret = jQuery.merge( this.constructor(), elems );
        ret.prevObject = this;
        return ret;
    },
    each: function( callback ) {
        return jQuery.each( this, callback );
    },
    map: function( callback ) {
        return this.pushStack( jQuery.map( this, function( elem, i ) {
            return callback.call( elem, i, elem );
        } ) );
    },
    slice: function() {
        return this.pushStack( slice.apply( this, arguments ) );
    },
    first: function() {
        return this.eq( 0 );
    },
    last: function() {
        return this.eq( -1 );
    },
    eq: function( i ) {
        var len = this.length,
            j = +i + ( i < 0 ? len : 0 );
        return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
    },
    end: function() {
        return this.prevObject || this.constructor();
    },
    push: push,
    sort: arr.sort,
    splice: arr.splice
}

总结:看这部分源码可以知道jQuery.fn就是jquery的原型对象

8.jQuery.fn.init( selector, context )具体做了什么?

//selector 选择器,可能是DOM对象、html字符串、jQuery对象
//context 选择器选择的范围
//rootjQuery == $(document);
init = jQuery.fn.init = function( selector, context, root ) {
        var match, elem;

        // 没有传选择器直接返回
        if ( !selector ) {
            return this;
        }

        root = root || rootjQuery;

        // 选择器传入的是字符串
        if ( typeof selector === "string" ) {
            if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {
                match = [ null, selector, null ];
        } else {
                match = rquickExpr.exec( selector );
        }

        if ( match && ( match[ 1 ] || !context ) ) {
                // HANDLE: $(html) -> $(array)
                if ( match[ 1 ] ) {
                    context = context instanceof jQuery ? context[ 0 ] : context;
                    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 ( 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 ] );

                    if ( elem ) {

                        // Inject the element directly into the jQuery object
                        this[ 0 ] = elem;
                        this.length = 1;
                    }
                    return this;
                }

            // HANDLE: $(expr, $(...))
            } 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)
        } else if ( selector.nodeType ) {
            this[ 0 ] = selector;
            this.length = 1;
            return this;

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

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

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

整体看摸不着头脑,抽离结构如下:

    init = jQuery.fn.init = function( selector, context, root ) {
        if ( typeof selector === "string" ) {
            //选择器类型是字符
            
        }else if(  selector.nodeType  ){
            //选择器类型是节点
            
        }else if( jQuery.isFunction( selector ) ){
            //简化$(document).ready(function(){});
            
        }
        
        //返回结果。
        return jQuery.makeArray( selector, this );
        }

抽离完了要想理解这些内容,首先来看看jquery到底支持哪些选择器selector?

1.$(document)   
2.$('<div>') 
3.$('.div') 
4.$('#test') 
5.$(function(){}) 
6.$("input:radio", document.forms[0]); 
7.$('input', $('div')) 
8.$() 
9.$("<div>", {  "class": "test" }).appendTo("body");

接着一个一个分支的看,它是如何支持这些选择器的,首先是typeof selector === "string"

if ( typeof selector === "string" ) {
            //传入的是标签类型,比如<p>
            if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {
                // 将html储存入match数组中,并与另一个分支中的正则捕获相对应
                match = [ null, selector, null ];
            } else {
                //放入正则进行匹配,结果类型是:[全匹配, <tag>, #id]
                //rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/
                //匹配HTML标记和ID表达式
                match = rquickExpr.exec( selector );
            }

            // 如果match不为空,并且match[1]也就是<tag>存在
            if ( match && ( match[ 1 ] || !context ) ) {
                if ( match[ 1 ] ) {
                    // 如果context是jQuery对象,则取其中的第一个DOM元素作为context
                    context = context instanceof jQuery ? context[ 0 ] : context;
                    // 将通过parseHTML处理生成的DOM对象merge进jQuery对象
                    jQuery.merge( this, jQuery.parseHTML(
                        match[ 1 ],
                        //如果context存在并且是note节点,则context就是的顶级节点或自身,否则content=document
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {
                            if ( isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }

                    return this;

                //如果是#id的形式,走这个分支进行处理
                } else {
                    //通过getEle方法获得DOM对象 将match[2]传入,是因为#id的形式是在第二个捕获组里面储存的。
                    elem = document.getElementById( match[ 2 ] );
                    // 如果该id元素存在
                    if ( elem ) {
                        // 将该元素保存进jQuery对象数组当中,并设置其length值为1
                        this[ 0 ] = elem;
                        this.length = 1;
                    }
                    return this;
                }

            } else if ( !context || context.jquery ) {
                //如果context不存在或者context是jQuery对象 
                //通过检测是不是有jquery属性
                // 进入Sizzle进行处理(复杂的选择器)
                return ( context || root ).find( selector );

            } else {
                //context存在并且context不是jQuery对象的情况 先调用$(context),在进入Sizzle进行处理
                return this.constructor( context ).find( selector );
            }

        }

接着是selector.nodeType分支

else if ( selector.nodeType ) {
    //直接将DOM元素存入jQuery对象并设置context和length
    this.context = this[0] = selector;
    this.length = 1;
    return this;
   }

最后是jQuery.isFunction( selector )分支

else if ( jQuery.isFunction( selector ) ) {
        //简化$(document).ready(function(){});
    return rootjQuery.ready( selector );
  }

分析了以上的分支,把jquery的选择器分别带进去走一下流程,首先是`3.$('div')
首先进入:

if ( typeof selector === "string" ) {}

接着进入下面的if分支:

if ( match && (match[1] || !context) ) {
    if ( match[1] ) {
        context = context instanceof jQuery ? context[0] : context;
        jQuery.merge( this, jQuery.parseHTML(
        match[1],
        context && context.nodeType ? context.ownerDocument || context : document,
        true
      ) );
    }
}

它进入了一个函数parseHTML()

jQuery.parseHTML = function( data, context, keepScripts ) {
    if ( typeof data !== "string" ) {
        return [];
    }
    if ( typeof context === "boolean" ) {
        keepScripts = context;
        context = false;
    }

    var base, parsed, scripts;

    if ( !context ) {
        if ( support.createHTMLDocument ) {
            context = document.implementation.createHTMLDocument( "" );
            base = context.createElement( "base" );
            base.href = document.location.href;
            context.head.appendChild( base );
        } else {
            context = document;
        }
    }
    //var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
    //匹配一个独立的标签
    parsed = rsingleTag.exec( data );
    scripts = !keepScripts && [];

    if ( parsed ) {
        return [ context.createElement( parsed[ 1 ] ) ];
    }
    //未通过节点的字符串,则通过创建一个div节点,将字符串置入div的innerHTML
    parsed = buildFragment( [ data ], context, scripts );

    if ( scripts && scripts.length ) {
        jQuery( scripts ).remove();
    }

    return jQuery.merge( [], parsed.childNodes );
};

最后返回this也就是jQuery

return this;

再看一下4.$("#id")
首先进入:

if ( typeof selector === "string" ) {}

接着进入下面的else分支进行正则匹配:

if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
    //不满足
}else{
    match = rquickExpr.exec( selector );
}

匹配完了再接着进入下面的else分支进行寻找添加:

if ( match[1] ) {
    //不满足
}else{
    elem = document.getElementById( match[2] );
    if ( elem ) {
   // 将该元素保存进jQuery对象数组当中,并设置其length值为1
   this.length = 1;
    this[0] = elem;
  }
}

最后返回this也就是jQuery

return this;

今儿先看到这!!!!


旧城
94 声望6 粉丝

每行代码都产生价值。


下一篇 »
从URL到页面