前端开发系列075-JQuery篇之框架源码解读[结构]

这篇文章将主要介绍jQuery框架的前面几百行代码并说明jQuery框架的整体结构。

一、源码解读

这里先简单贴出jQuery框架3.3.1版本中的前600行代码,其它和整体结构无关的部分省略了。


 * jQuery JavaScript Library v3.3.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: 2018-01-20T17:24Z			    更新(发布)时间

//jQuery的外城结构是一个闭包(即时调用函数)
//整体结构可以抽象为(fn)(....)
( function( global, factory ) {

	"use strict"; 	//开启严格模式

	//判断的当前的环境是否是CommonJs
	//说明:CommJs 环境中会有一个 module 对象,这个对象上会有一个 exports 对象
	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.

		//如果在 CommonJs 环境下,那么将 jQuery 对象挂载到 module.exports 对象
		//factory函数的第二个参数传递为true,表示将不会在window上注册jQuery对象

		//因为可能运行在非浏览器下,所有对global.document进行检查
		//如果global.document有值,那么调用factory( global, true )得到结果赋值给module.exports
		//如果global.document没有值(在非浏览器环境下),我们没有window对象,那么就需要自己创建了一个模拟浏览器的环境
		//传入自己的 window 对象,这种情况下 jQuery 对象将绑定到你传入的这个特殊的 window 对象上
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					//如果没有document,那么就抛出错误信息
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	}
	else
	{
		//如果不是在 CommonJs 环境下,直接执行工厂函数
        //调用函数的时候传递一个参数(window|this),第二个参数没有传值,默认为undefined
		factory( global );
	}

	// Pass this if window is not defined yet
    //实参说明:第一个参数(global)的值为window 或者 是当前上下文this,第二个参数为函数
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal )
{

// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
//开启严格模式
"use strict";

//声明变量
var arr = [];

//获取document文件
var document = window.document;

//保存Object对象上获取原型对象的方法
var getProto = Object.getPrototypeOf;

//保存数组中截取元素的方法
var slice = arr.slice;

//保存数组中合并数组的方法
var concat = arr.concat;

//保存数组中添加元素的方法
var push = arr.push;

//保存数组中返回元素索引的方法
var indexOf = arr.indexOf;

//初始化空的对象
var class2type = {};

//保存{}.toString方法,其实是Object.prototype.toString方法
//该方法用于获取指定对象的类型和真实构造函数 ex: [object String]
var toString = class2type.toString;

//保存检查是否是实例成员的方法
var hasOwn = class2type.hasOwnProperty;

//保存 Object.prototype.hasOwnProperty.toString方法
var fnToString = hasOwn.toString;
//保存 Object.prototype.hasOwnProperty.toString.call(Object) 方法
//	==> "function Object() { [native code] }"
var ObjectFunctionString = fnToString.call( Object );

// 初始化空的对象
var support = {};
// 工具函数:检查传入的对象是否是函数类型的
var isFunction = function isFunction( obj ) {

      // Support: Chrome <=57, Firefox <=52
	  //浏览器的支持情况
      // In some browsers, typeof returns "function" for HTML <object> elements
	  // 在很多的浏览器中对HTML对象节点执行typeof会返回 function
      // (i.e., `typeof document.createElement( "object" ) === "function"`).
	  // 在ie中typeof document.createElement( "object" )的结果为function
      // We don't want to classify *any* DOM node as a function.
	  // 在检查函数的时候排除任何的DOM节点
	  // 注:DOM节点均拥有nodeType属性,属性值为number类型,根据具体的数值不同来进行区分 1为元素节点 2为属性节点

      return typeof obj === "function" && typeof obj.nodeType !== "number";
  };

//工具函数:检查传入的参数是否是window
var isWindow = function isWindow( obj ) {
		//检查window的方式  window = window.window 即window对象本身拥有window属性来标名自己是window
		//null不能拥有任何的属性,排除null
		return obj != null && obj === obj.window;
};

//保存script属性的字面量对象:类型|资源|noModule
var preservedScriptAttributes = {
		type: true,
		src: true,
		noModule: true
	};

// 该方法作为 $.globalEval();方法的内部实现,作用类似于js原生的eval方法
// $.globalEval( "var a = 1;" );方法其实就是调用 DOMEval("var a = 1;");
function DOMEval( code, doc, node ) {

	     //如果doc没有值,那么初始化为document
		doc = doc || document;

		//创建空的script标签
		var i,
			script = doc.createElement( "script" );
		//设置script标签的文本内容
		script.text = code;
		if ( node ) {
			//循环preservedScriptAttributes对象
			//把node节点中的type && src && noModule拷贝给script标签
			for ( i in preservedScriptAttributes ) {
				if ( node[ i ] ) {
					script[ i ] = node[ i ];
				}
			}
		}
		//doc.head 表示访问页面中的header头部标签
		//把先创建的script标签插入到页面然后删除掉
		doc.head.appendChild( script ).parentNode.removeChild( script );
	}

//	获取参数对应的数据类型
function toType( obj ) {
	// 如果参数是null,那么就返回"null"
	if ( obj == null ) {
		return obj + "";
	}
	// Support: Android <=2.3 only (functionish RegExp)
	// 如果typeof的结果为object 或者是function 那么就通过class2type[ toString.call( obj ) ]方法计算
	// class2type[ toString.call( obj ) ] 其实就是Object.prototype.toString.call(obj) 形式
	// 如果toString方法计算的结果为false,那么就返回object,否则返回typeof obj的值
	return typeof obj === "object" || typeof obj === "function" ?
		class2type[ toString.call( obj ) ] || "object" :
		typeof obj;
}
/* global Symbol */
// Defining this global in .eslintrc.json would create a danger of using the global
// unguarded in another place, it seems safer to define global only for this module


var
	// 当前版本
	version = "3.3.1",

	// Define a local copy of jQuery 定义jQuery的本地副本
	// jQuery工厂函数的定义(声明)
	// 参数1 : 选择器
	// 参数2 :  上下文对象
	jQuery = function( selector, context ) {

		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)

		// jQuery.fn,init 作为构造函数,这里返回的其实是jQuery.fn.init构造函数的实例化对象
		// 调用jQuery函数的时候其实是以构造函数的方式调用 xxx...init函数
		return new jQuery.fn.init( selector, context );
	},

	// Support: Android <=4.0 only
	// Make sure we trim BOM and NBSP
	// 确保对BOM和NBSP的处理,清除字符串开始和结尾的一个或多个空格的正则表达式
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;

//设置jQuery的原型对象,并把原型对象赋值给jQuery.fn
jQuery.fn = jQuery.prototype = {

	//这里列出了一部分jQuery原型成员(属性和方法)
	//所有的jQuery实例对象都能够访问这些属性和方法

	// The current version of jQuery being used
	//当前正在使用的jQuery版本信息
	jquery: version,
	//构造器属性 ---> jQuery
	constructor: jQuery,

	// The default length of a jQuery object is 0
	//jQuery实例对象中数据的个数,默认长度为0
	length: 0,

	// 把jQuery实例对象转换为数组类型的方法
	toArray: function() {
		//其实调用的是Array.prototype.slice.call(this) 方法 相当于this.slice()
		//在数组的slice方法中,如果不接受参数则表示截取所有的元素保存到一个新的数组中返回
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	// 获取jQ对象中指定索引对应的数据(通常为DOM节点)
	get: function( num ) {

		// Return all the elements in a clean array
		//如果没有传递参数,那么等价于调用了toArray方法
		//把当前jQ对象中所有的value值保存到数组中返回
		if ( num == null ) {
			return slice.call( this );
		}

		// Return just the one element from the set
		// 区分索引值的情况
		// 如果索引值为负数那么 返回this[index + this.length]
		// 如果索引值> = 0     返回thus[index]
		return num < 0 ? this[ num + this.length ] : this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	// 维护堆栈集合 把传入的数据包裹成一个新的jQ对象,然后更新prevObject的值为上一个(this)对象
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		// merge方法用于合并两个数组
		// this.constructor() 其实就是this.jQuery(); 得到的是一个空的jQ实例对象
		//
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)

		//prevObject属性用于维护和记录前一个操作的jQ实例对象
		//把当前对象设置为ret的prevObject属性,并返回
		ret.prevObject = this;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	//迭代jQuery实例对象的方法,该方法内部调用jQuery.each方法实现
	each: function( callback ) {
		return jQuery.each( this, callback );
	},

	// 数组映射方法,对jQuery.map方法做了额外的包装
	map: function( callback ) {
		return this.pushStack( jQuery.map( this, function( elem, i ) {
			//使用当前的元素来调用callback方法,绑定this
			return callback.call( elem, i, elem );
		} ) );
	},
	// 截取对象中指定的键值对(元素)
	//因为slice方法调用后返回的是一个新的对象集合,所以需要调用pushStack方法维护prevObject堆栈
	slice: function() {
		//核心实现:this => Array.prototype.slice(arguments) || this => [].slice(arguments)
		return this.pushStack( slice.apply( this, arguments ) );
	},

	//获取jQuery实例对象中的第一个元素(第一个键值对中的value值,其实就是第一个DOM标签)包裹为jQuery对象返回
	//内部通过调用jQuery.eq方法实现,
	first: function() {
		return this.eq( 0 );
	},
	//获取jQuery实例对象中的最后一个元素
	//同first方法一致,eq方法传递-1表示倒着数,即倒数第一个(最后一个)元素
	last: function() {
		return this.eq( -1 );
	},

	//获取jQuery实例对象中指定索引对应的元素,拿到元素后包装为jQuery实例对象返回
	//该方法的参数支持正整数或者是负数
	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );

		//自己写的另外一种实现方案(在判断的时候需要考虑到越界的问题)
		// var len = this.length;
		// var arrM = [];
		// if (i >= 0 && i < len)
		// {
		// 		arrM.push(this[i])
		// }
		// else if(i < 0 && (-i < len) )
		// {
		// 		arrM.push(this[i + len])
		// }
		// return this.pushStack(arrM)
	},

	//返回前一个操作的jQuery实例对象
	end: function() {
		//检查prevObject的属性值,如果优质那么就返回prevObject否则返回空的jQuery实例对象
		return this.prevObject || this.constructor();
	},

	// For internal use only.
	// 仅供内部使用的方法
	// Behaves like an Array's method, not like a jQuery method.
	// 这些方法的表现和数组一致,不完全像jQuery风格的方法

	//往jQuery实例对象中添加数据
	push: push,
	//排序的方法
	sort: arr.sort,
	//添加|删除的方法,其实就是数组中的splice方法
	splice: arr.splice
};

//......
// 设置jQuery.prototype.init方法
init = jQuery.fn.init = function( selector, context, root ) {
    //...
};

// Give the init function the jQuery prototype for later instantiation
/  让jQuery.prototype.init方法的原型对象指向jQuery原型对象
// (正因如此$("xx")得到的jQ实例对象才能访问jQuery原型对象上面的方法)
init.prototype = jQuery.fn;
//.....

window.jQuery = window.$ = jQuery;
} );

二、框架的整体结构

下面给出简化后jQuery框架的整体结构。

//01 立即调用函数(闭包)
(function (window) {

    //02 提供工厂函数
    var jQuery = function (selector) {
        return new jQuery.fn.init(selector);
    }

    //03 设置原型对象
    jQuery.prototype = {
        constructor:jQuery,
        init:function (selector) {
            //初始化处理...
        }
    }

    //动态的添加fn属性
    jQuery.fn = jQuery.prototype;
    
    //04 原型对象赋值
    jQuery.fn.init.prototype = jQuery.fn;

    //........
    
    //05 把jQuery和$暴露出来
    window.$ = window.jQuery = jQuery;

})(window);

jQuery框架整体结构总结

>❏ &nbsp;&nbsp;jQuery本身是闭包中的一个函数,该函数作工厂函数用。
>❏ &nbsp;&nbsp;jQuery所有的代码都被放在一个即时调用函数中(闭包),拥有独立和安全的作用域。
>❏ &nbsp;&nbsp;jQuery方法在调用的时候,获取的实例对象其本质上是`jQuery.fn.init`构造函数实例化的。
>❏ &nbsp;&nbsp;jQuery函数调用的时候,参数实际上都传递给了`init`函数,`init`函数才是真正的入口函数。
> ❏ &nbsp;&nbsp;为了让实例对象访问jQuery原型对象的成员,设置了`jQuery.fn.init`和`jQuery原型对象`共享。
>❏ &nbsp;&nbsp;jQuery通过把自身赋值给window成为全局对象的属性来实现框架外的访问(`$ 和 jQuery`访问)。

posted on 2022-12-15 08:56  文顶顶  阅读(37)  评论(0编辑  收藏  举报

导航