jQuery源码解析(一):读懂源码结构、改变固有思维方式

798 阅读6分钟

前端必备:熟读框架源码,今天来分析jQuery的源码

下载源码

我们想要读一个框架的源码,那第一步肯定是下载源码(这是废话)。

地址:jQuery.js(本文代码版本为jquery3.4.1

点击上面地址,将代码粘贴到自己的本地编辑器中,然后开始进行下一步工作。

源码结构

这一步,我们分析代码,研究代码结构

我们通过观察源码的结构,将代码分割成了三个部分

    /*
    * 第一部分:匿名函数结构
    */
    /*!
     * jQuery JavaScript Library v3.4.1
     * https://jquery.com/
     *
     * Includes Sizzle.js
     * https://sizzlejs.com/
     *
     * Copyright JS Foundation and other contributors
     * Released under the MIT license
     * https://jquery.org/license
     *
     * Date: 2019-05-01T21:04Z
     */
    ( function( global, factory ) {

    	"use strict";
    
    	if ( typeof module === "object" && typeof module.exports === "object" ) {
    
    		// For CommonJS and CommonJS-like environments where a proper `window`
    		// is present, execute the factory and get jQuery.
    		// For environments that do not have a `window` with a `document`
    		// (such as Node.js), expose a factory as module.exports.
    		// This accentuates the need for the creation of a real `window`.
    		// e.g. var jQuery = require("jquery")(window);
    		// See ticket #14549 for more info.
    		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 );
    	}

    // Pass this if window is not defined yet
    } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
    /*
    * 第二部分:功能代码(省略)
    */
    
    
    // 第三部分:返回
    // Register as a named AMD module, since jQuery can be concatenated with other
    // files that may use define, but not via a proper concatenation script that
    // understands anonymous AMD modules. A named AMD is safest and most robust
    // way to register. Lowercase jquery is used because AMD module names are
    // derived from file names, and jQuery is normally delivered in a lowercase
    // file name. Do this after creating the global so that if an AMD module wants
    // to call noConflict to hide this version of jQuery, it will work.
    
    // Note that for maximum portability, libraries that are not jQuery should
    // declare themselves as anonymous modules, and avoid setting a global if an
    // AMD loader is present. jQuery is a special case. For more information, see
    // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
    
    if ( typeof define === "function" && define.amd ) {
    	define( "jquery", [], function() {
    		return jQuery;
    	} );
    }




    var

	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$;

    jQuery.noConflict = function( deep ) {
    	if ( window.$ === jQuery ) {
    		window.$ = _$;
    	}

    	if ( deep && window.jQuery === jQuery ) {
    		window.jQuery = _jQuery;
    	}
	    return jQuery;
    };

    // Expose jQuery and $ identifiers, even in AMD
    // (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
    // and CommonJS for browser emulators (#13566)
    if ( !noGlobal ) {
    	window.jQuery = window.$ = jQuery;
    }




    return jQuery;
});

首先介绍一下匿名函数:

①匿名函数的结构

    /*
    * 第一个括号内为匿名函数
    * 第二个括号用于调用函数,并传参
    */
    (function(x,y){
        
    })(x,y)

②匿名函数的作用

  • 匿名函数最主要的作用是创建闭包【为什么要使用闭包】
  • 闭包指的是有权访问另一个函数作用域中的变量函数,也就是说函数内部再创建一个函数
    for(var i = 0;i <= 10;i++) {
        setTimeout(() => {
            console.log(i);
        }, 1000)
    }

我们执行上面这一段代码,结果是输出了1111,如下图:

那为什么会这样?我们知道,for循环执行的速度要比setTimeout快得多,所以当跳出for循环时才可以执行到setTimeout里面的函数中(console.log(i)),所以执行上述代码片段,输出的是我们所看到的结果。

所以,现在我们想输出1-10,那我们应该怎么做?

一般有两种方式:

I、一种是用es6中的声明变量关键字let代替var,我们来看一下效果

II、另一种就是使用匿名函数

    for(var i = 0;i <= 10;i++) {
        (function(i){
            setTimeout(function(){
                console.log(i);
            }, 1000)
        })(i)
    }

在这个方法中,我们使用了匿名函数,匿名函数传入参数i,并接收,这个时候也得到了我们想要的0-10的结果,如下图

  • 为什么要使用匿名函数

①匿名函数可以自动执行

②提供模块化,将变量传入,为全局变量提供局部范围,可以减少查找时的开销量等。

介绍完上述基本概念,下面进行代码解析

代码分析

一、第一部分

1.1 先来看第一大段注释部分

这么一段很长的注释包括了:jQuery库的版本以及地址、包含的库Sizzle.js及官网地址,其中这个Sizzle.js是一个纯JavaScript css选择器引擎、版权说明、以及发布日期

1.2 接下来看匿名函数部分

1.2.1 它传入了两个参数并接收

  • typeof window !== "undefined" ? window : this 这里对浏览器是否支持window环境做了一个判断,假如不支持,则传入this对象

  • function( window, noGlobal ) {} 这里传入了一个函数,windownoGloabl作为参数,其实这个函数就是jQuery的功能函数部分

  • 同时接收了上面两个参数(global对象和factory函数)。

1.2.2 "use strict"

  • 这两个单词的意思是,使用了严格模式,即在严格模式下运行代码(建议加上~)。

1.2.3 我们看下面这一段代码,if-else语句中判断了module的类型,其实这段代码的含义,就是我们在使用jQuery框架的时候,是用了哪种方式引入:

    if ( typeof module === "object" && typeof module.exports === "object" ) {
		// 先省略
	} else {
		factory( global );
	}
  • 假如,我们这样用script标签引入:

<script src="jQuery.js"></script>

这个时候,是检测不到module的,也就是module的值为undefined,那么这个时候代码自然而然的就执行了else语句,也就是直接调用了factory函数,并在函数中传入this或是Windows对象。

  • 然后,我们这样用require引入jQuery
    const jquery = require('jquery');

我们可以尝试输出一下module,得到了这样的结果

可以看到,module不再是undefined,而是一个json类型的数据,这个时候也就执行了if-else语句中的if条件下的语句

1.2.4 接下来,我们看if条件下到底写了些什么?

    // For CommonJS and CommonJS-like environments where a proper `window`
    // is present, execute the factory and get jQuery.
    // For environments that do not have a `window` with a `document`
    // (such as Node.js), expose a factory as module.exports.
    // This accentuates the need for the creation of a real `window`.
    // e.g. var jQuery = require("jquery")(window);
    // See ticket #14549 for more info.
    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 );
    	};
  • 先看注释
    //对于CommonJS和类似于CommonJS的环境,其中有一个适当的window
    //当前,执行factory并获得jQuery。
    //对于没有带有document的window的环境
    //(例如Node.js),将工厂公开为module.exports。
    //这强调了创建一个真正的window的必要性。
    //例如var jQuery = require("jQuery")(window);

这里会判断global里面有没有document属性,如果有,就会执行factory函数,如果没有就会抛出一个异常(jQuery requires a window with a document[jQuery需要一个带有documentwindow]),这个时候通常是所处的浏览器环境不支持使用jQuery框架。

二、第三部分

我们仔细观察这一部分,就会发现:这一部分其实就是处理向外暴露jQuery和$标识符,代码在这一部分判断了很多种情况,这里的处理便于大众使用jQuery框架。

三、第二部分

这一部分是功能代码部分,大概有一万多行的代码,看这一部分的代码,要学会人家的编程思想以及编写一个框架的思维方式

PS: 建议像我这样没有把jQuery API全记下来的群体,可以对照着API文档看源码,这样能够更好的理解、了解功能的实现过程,也能够更好的记住这些API

总结

我们通过上述的步骤,分析了jQuery源码,了解了一个框架的基本结构,也知道了它为什么会这样写框架。我们也就可以通过上面的方法,不妨可以尝试写出来一个属于自己的库~

对于还是一个小白的我来讲,写出来的文章会有不好、甚至错误的地方,还希望大家积极指正~让小白快速成长~