前端开发系列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);
![](http://img.wendingding.vip/blog/jq_02.png)
jQuery框架整体结构总结
>❏ jQuery本身是闭包中的一个函数,该函数作工厂函数用。
>❏ jQuery所有的代码都被放在一个即时调用函数中(闭包),拥有独立和安全的作用域。
>❏ jQuery方法在调用的时候,获取的实例对象其本质上是`jQuery.fn.init`构造函数实例化的。
>❏ jQuery函数调用的时候,参数实际上都传递给了`init`函数,`init`函数才是真正的入口函数。
> ❏ 为了让实例对象访问jQuery原型对象的成员,设置了`jQuery.fn.init`和`jQuery原型对象`共享。
>❏ jQuery通过把自身赋值给window成为全局对象的属性来实现框架外的访问(`$ 和 jQuery`访问)。