资讯专栏INFORMATION COLUMN

jQuery3.3.1源码阅读(一)

王晗 / 1987人阅读

摘要:入口结构具体代码抽离结构如下涉及到的知识解析函数时的规则函数定义和函数表达式闭包解析函数的规则解析器会在遇到时将其认为是函数定义而非函数表达式函数定义和函数表达式函数定义函数表达式闭包闭包函数中的函数,本质是指作用域内的作用域闭包举例综合以

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(){ })()

第一个括号有两个作用:

让js解析器把后面的function当作函数表达式而不是函数定义

形成一个作用域,类似在上面闭包例子中的f函数

第二个括号

触发函数并传参

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

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

if ( typeof selector === "string" ) {
            //传入的是标签类型,比如

if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // 将html储存入match数组中,并与另一个分支中的正则捕获相对应 match = [ null, selector, null ]; } else { //放入正则进行匹配,结果类型是:[全匹配, , #id] //rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]+))$/ //匹配HTML标记和ID表达式 match = rquickExpr.exec( selector ); } // 如果match不为空,并且match[1]也就是存在 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][^/>:x20	
f]*)[x20	
f]*/?>(?:|)$/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;

今儿先看到这!!!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103442.html

相关文章

  • 篇文章把本该属于你的源码天赋还给你

    摘要:一些方法不应该这样不应该漫无目的地随手拿起一分源码,试图去通读。应该这样精心挑选要阅读的源码项目。这最好是与你的编程语言你的工作内容你的兴趣所在相关的,这样才能更切实地感受到阅读源码给你带来的益处,更有动力继续。 showImg(https://segmentfault.com/img/bVbcvmm?w=785&h=525); 怎么阅读源码 没有经验的技术差底子薄的初级程序员,如何阅...

    chanjarster 评论0 收藏0
  • 精读《源码学习》

    摘要:精读原文介绍了学习源码的两个技巧,并利用实例说明了源码学习过程中可以学到许多周边知识,都让我们受益匪浅。讨论地址是精读源码学习如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。 1. 引言 javascript-knowledge-reading-source-code 这篇文章介绍了阅读源码的重要性,精读系列也已有八期源码系列文章,分别是: 精读《Immer.js》源...

    aboutU 评论0 收藏0
  • TiDB Binlog 源码阅读系列文章()序

    摘要:总体而言,读者需要有一定的使用经验,以及可以读懂语言程序。内容概要本篇作为源码阅读系列文章的序篇,会简单的给大家讲一下后续会讲哪些部分以及逻辑顺序,方便大家对本系列文章有整体的了解。小结本篇文章主要介绍了源码阅读系列文章的目的和规划。 作者:黄佳豪 TiDB Binlog 组件用于收集 TiDB 的 binlog,并准实时同步给下游,如 TiDB、MySQL 等。该组件在功能上类似于 ...

    whidy 评论0 收藏0

发表评论

0条评论

王晗

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<