/*! * jQuery JavaScript Library v1.7.2 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Wed Mar 21 12:46:34 2012 -0700 */ (function( window, undefined ) { // 使用与窗口参数相对应的正确文档(沙箱环境) var document = window.document, // 获取当前窗口的文档对象 navigator = window.navigator, // 获取浏览器的导航信息 location = window.location; // 获取当前文档的地址信息 var jQuery = (function() { // 定义一个局部的 jQuery 函数 var jQuery = function( selector, context ) { // jQuery 对象实际上是经过增强的 init 构造函数 return new jQuery.fn.init( selector, context, rootjQuery ); // 返回初始化后的 jQuery 实例 }, // 在窗口对象中保存 jQuery 的引用,以防被覆盖 _jQuery = window.jQuery, // 在窗口对象中保存 $ 的引用,以防被覆盖 _$ = window.$, // 指向根 jQuery(document) 的中央引用 rootjQuery, // 简单检查 HTML 字符串或 ID 字符串的正则表达式 // 优先考虑 #id 以避免通过 location.hash 引发 XSS (安全漏洞 #9521) quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // 检查字符串是否包含非空白字符的正则表达式 rnotwhite = /\S/, // 用于修剪左右两端空白字符的正则表达式 trimLeft = /^\s+/, trimRight = /\s+$/, // 匹配独立标签的正则表达式 rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, // JSON 验证相关的正则表达式 rvalidchars = /^[\],:{}\s]*$/, // 验证 JSON 字符串中的字符 rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, // 匹配有效的 JSON 转义字符 rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, // 匹配 JSON 中的有效令牌 rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, // 匹配 JSON 中的括号 // 用户代理相关的正则表达式,用于检测浏览器类型 rwebkit = /(webkit)[ \/]([\w.]+)/, // 检测 WebKit 浏览器 ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, // 检测 Opera 浏览器 rmsie = /(msie) ([\w.]+)/, // 检测 Internet Explorer 浏览器 rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, // 检测 Mozilla 浏览器 // 匹配带有短横线的字符串以进行驼峰化 rdashAlpha = /-([a-z]|[0-9])/ig, // 匹配短横线后跟字母或数字的模式 rmsPrefix = /^-ms-/, // 匹配以 -ms- 开头的前缀 // 被 jQuery.camelCase 使用的回调函数,用于字符串替换 fcamelCase = function( all, letter ) { return ( letter + "" ).toUpperCase(); // 将匹配到的字母转换为大写 }, // 保存用户代理字符串,以供 jQuery.browser 使用 userAgent = navigator.userAgent, // 用于匹配浏览器的引擎和版本 browserMatch, // 用于 DOM 准备就绪的延迟对象 readyList, // 准备就绪事件处理程序 DOMContentLoaded, // 保存对一些核心方法的引用 toString = Object.prototype.toString, // 获取对象的字符串表示 hasOwn = Object.prototype.hasOwnProperty, // 检查对象是否有指定的属性 push = Array.prototype.push, // 数组的 push 方法 slice = Array.prototype.slice, // 数组的 slice 方法 trim = String.prototype.trim, // 字符串的 trim 方法,去除空白字符 indexOf = Array.prototype.indexOf, // 数组的 indexOf 方法,用于查找元素 // [[Class]] -> 类型对应关系的对象 class2type = {}; // 用于存储不同数据类型的映射关系 // jQuery.fn 和 jQuery.prototype 定义了 jQuery 的原型 jQuery.fn = jQuery.prototype = { constructor: jQuery, // 构造函数指向 jQuery init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // 定义局部变量 // 处理空选择器 $(""), $(null), 或 $(undefined) if ( !selector ) { return this; // 返回当前对象 } // 处理 $(DOMElement),当传入的是一个 DOM 元素时 if ( selector.nodeType ) { this.context = this[0] = selector; // 将上下文和第一个元素设置为该元素 this.length = 1; // 设置长度为 1 return this; // 返回当前对象 } // body 元素只存在一次,优化查找过程 if ( selector === "body" && !context && document.body ) { this.context = document; // 设置上下文为文档 this[0] = document.body; // 将第一个元素设置为 body 元素 this.selector = selector; // 保存选择器字符串 this.length = 1; // 设置长度为 1 return this; // 返回当前对象 } // 处理 HTML 字符串 if ( typeof selector === "string" ) { // 判断我们是处理 HTML 字符串还是 ID if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // 假设以 <> 开头和结尾的字符串是 HTML,因此跳过正则检查 match = [ null, selector, null ]; // 创建一个匹配数组 } else { match = quickExpr.exec( selector ); // 使用正则表达式检查选择器 } // 验证匹配,并且在指定 ID 时未提供上下文 if ( match && (match[1] || !context) ) { // 处理: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; // 如果上下文是 jQuery 对象,则取其第一个元素 doc = ( context ? context.ownerDocument || context : document ); // 获取文档对象 // 如果传入的是单个字符串且它是单个标签 // 则直接使用 createElement 跳过剩余处理 ret = rsingleTag.exec( selector ); // 检查是否为单一标签并执行相应操作 if ( ret ) { // 如果 ret 存在,表示我们匹配到了一个 HTML 标签 if ( jQuery.isPlainObject( context ) ) { // 如果上下文是一个普通对象 selector = [ document.createElement( ret[1] ) ]; // 创建一个新的元素,并将其放入选择器数组中 jQuery.fn.attr.call( selector, context, true ); // 为新创建的元素设置属性 } else { selector = [ doc.createElement( ret[1] ) ]; // 否则,仅仅创建元素并放入选择器数组中 } } else { // 如果没有匹配到 HTML 标签 ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); // 构建一个文档片段 selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; // 从片段中获取子节点 } return jQuery.merge( this, selector ); // 将新创建的选择器与当前 jQuery 对象合并 // 处理: $("#id") } else { // 如果没有匹配到 HTML,检查是否为 ID 选择器 elem = document.getElementById( match[2] ); // 根据 ID 获取元素 // 检查 parentNode,以捕获当 Blackberry 4.6 返回 // 不再在文档中的节点 #6963 if ( elem && elem.parentNode ) { // 如果元素存在且它有父节点 // 处理 IE 和 Opera 可能会根据名称而非 ID 返回项的情况 if ( elem.id !== match[2] ) { // 如果元素的 ID 与匹配的 ID 不相等 return rootjQuery.find( selector ); // 使用 jQuery 的查找方法进行搜索 } // 否则,将元素直接注入到 jQuery 对象中 this.length = 1; // 设置长度为 1 this[0] = elem; // 将找到的元素放入 jQuery 对象中 } this.context = document; // 设置上下文为文档 this.selector = selector; // 保存选择器字符串 return this; // 返回当前 jQuery 对象 } // 处理: $(expr, $(...)) } else if ( !context || context.jquery ) { // 如果没有上下文或上下文是一个 jQuery 对象,使用根 jQuery 对象查找选择器 return ( context || rootjQuery ).find( selector ); // 处理: $(expr, context) // 这相当于: $(context).find(expr) } else { // 在指定的上下文中查找选择器 return this.constructor( context ).find( selector ); } // 处理: $(function) // 简化文档就绪的快捷方式 } else if ( jQuery.isFunction( selector ) ) { // 如果选择器是一个函数,调用根 jQuery 对象的 ready 方法 return rootjQuery.ready( selector ); } // 如果选择器对象有 selector 属性,则保存上下文和选择器 if ( selector.selector !== undefined ) { this.selector = selector.selector;// 保存选择器字符串 this.context = selector.context;// 保存上下文 } // 将选择器转化为数组,并返回当前 jQuery 对象 return jQuery.makeArray( selector, this ); }, // 选择器初始为空字符串 selector: "", // 当前 jQuery 版本 jquery: "1.7.2", // jQuery 对象的默认长度为 0 length: 0, // 获取匹配元素集合中的元素数量 size: function() { return this.length; // 返回当前 jQuery 对象的长度 }, toArray: function() { // 将 jQuery 对象转换为原生数组并返回 return slice.call( this, 0 ); }, // 获取匹配元素集合中的第 N 个元素,或返回整个匹配元素集合的干净数组 get: function( num ) { return num == null ? // 如果未提供参数,返回一个“干净”的数组 this.toArray() : // 返回特定索引的对象,如果是负数则从后向前获取 ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // 接受一个元素数组并将其推入栈中(返回新的匹配元素集合) pushStack: function( elems, name, selector ) { // 创建一个新的 jQuery 匹配元素集合 var ret = this.constructor(); // 如果传入的元素是一个数组,则将其添加到新集合中 if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { // 否则,将单个元素合并到新集合中 jQuery.merge( ret, elems ); } // 将旧对象作为引用添加到栈中 ret.prevObject = this; // 保存上下文 ret.context = this.context; // 根据名称和选择器更新新的选择器字符串 if ( name === "find" ) { // 如果名称为 find,更新选择器为原选择器加上新选择器 ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; } else if ( name ) { // 否则,格式化选择器为 "原选择器.方法名(选择器)" ret.selector = this.selector + "." + name + "(" + selector + ")"; } // 返回新形成的元素集合 return ret; }, // 为匹配集合中的每个元素执行回调函数。 // (你可以用一个数组作为参数进行初始化,但这仅在内部使用。) each: function( callback, args ) { return jQuery.each( this, callback, args ); // 调用 jQuery 的 each 方法,传入当前对象和回调 }, ready: function( fn ) { // 绑定文档就绪事件的监听器 jQuery.bindReady(); // 添加回调函数到就绪列表中 readyList.add( fn ); return this; // 返回当前对象以便链式调用 }, eq: function( i ) { i = +i; // 将输入转换为数字 return i === -1 ? // 如果输入是 -1,返回整个集合 this.slice( i ) : // 从集合的末尾返回元素 this.slice( i, i + 1 ); // 否则返回索引为 i 的单个元素 }, first: function() { return this.eq( 0 ); // 返回集合中的第一个元素 }, last: function() { return this.eq( -1 ); // 返回集合中的最后一个元素 }, slice: function() { // 使用 slice 方法从当前集合中切片,并返回新的匹配集合 return this.pushStack( slice.apply( this, arguments ), // 应用 slice 函数于当前集合 "slice", // 标识操作类型为 "slice" slice.call(arguments).join(",") // 组合切片参数并作为选择器 ); }, map: function( callback ) { // 将回调应用于集合中的每个元素,并返回新的匹配集合 return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); // 在每个元素上调用回调 })); }, end: function() { // 返回上一个 jQuery 对象或一个空的 jQuery 对象 return this.prevObject || this.constructor(null); }, // 仅供内部使用。 // 行为类似于数组的方法,而不是 jQuery 方法 push: push, // 将 push 方法赋给当前对象 sort: [].sort, // 将原生数组的 sort 方法赋给当前对象 splice: [].splice // 将原生数组的 splice 方法赋给当前对象 }; // 将 init 函数的原型设置为 jQuery 的原型,以便后续实例化时使用 jQuery.fn.init.prototype = jQuery.fn; // 扩展 jQuery 和 jQuery.fn 的方法 jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, // 声明变量用于存储 target = arguments[0] || {}, // 第一个参数作为目标对象,默认为空对象 i = 1, // 从第二个参数开始遍历 length = arguments.length, // 参数总数 deep = false; // 深拷贝标志,默认不深拷贝 // 处理深拷贝情况 if ( typeof target === "boolean" ) { // 如果第一个参数是布尔值 deep = target; // 记录是否深拷贝 target = arguments[1] || {}; // 将第二个参数作为目标对象 // 跳过布尔值和目标对象 i = 2; // 从第三个参数开始 } // 处理目标为字符串或其他类型的情况(可能在深拷贝时发生) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; // 如果目标不是对象或函数,则将其重置为空对象 } // 如果只有一个参数,扩展 jQuery 本身 if ( length === i ) { target = this;// 将目标设置为当前对象 --i;// 参数索引减一 } for ( ; i < length; i++ ) { // 迭代参数列表,从第 i 个元素开始,直到 length // 只处理非空值(非 null 和 undefined) if ( (options = arguments[ i ]) != null ) { // 获取当前参数,并确保它不为 null 或 undefined // 扩展目标对象 for ( name in options ) { // 遍历 options 对象的每个属性 src = target[ name ]; // 获取目标对象中对应属性的值 copy = options[ name ]; // 获取当前选项对象中对应属性的值 // 防止无限循环 if ( target === copy ) { // 如果源和目标是同一个对象,跳过 continue; } // 如果正在合并普通对象或数组,则递归 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { // 检查是否需要深拷贝以及当前属性是否为对象或数组 if ( copyIsArray ) { // 如果是数组 copyIsArray = false; // 重置标志 clone = src && jQuery.isArray(src) ? src : []; // 如果目标对象中已有数组,则使用它,否则初始化一个空数组 } else { // 如果是普通对象 clone = src && jQuery.isPlainObject(src) ? src : {}; // 如果目标对象中已有对象,则使用它,否则初始化一个空对象 } // 永远不要直接移动原始对象,进行克隆 target[ name ] = jQuery.extend( deep, clone, copy ); // 使用 jQuery.extend 方法深度合并克隆的对象与复制的对象 // 不要引入 undefined 值 } else if ( copy !== undefined ) { // 如果 copy 不是 undefined,将其赋值给目标对象 target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend({ // noConflict 方法用于解除 jQuery 的冲突,使其与其他库共存 noConflict: function (deep) { // 如果全局变量 $ 是 jQuery,则将其恢复为之前的值 _$ if (window.$ === jQuery) { window.$ = _$; // 恢复原来的 $ 符号 } // 如果深度冲突处理且全局变量 jQuery 是当前的 jQuery,则将其恢复为之前的值 _jQuery if (deep && window.jQuery === jQuery) { window.jQuery = _jQuery; // 恢复原来的 jQuery 符号 } return jQuery; // 返回当前的 jQuery 对象 }, // 用于跟踪 DOM 是否准备好可供使用,初始值为 false isReady: false, // 用于计数等待多少个事件触发后 ready 事件会被触发,初始值为 1 readyWait: 1, // holdReady 方法用于控制 ready 事件的触发 holdReady: function (hold) { if (hold) { // 如果请求保持状态,则增加 readyWait 计数 jQuery.readyWait++; } else { // 否则,触发 ready 事件并将参数设置为 true jQuery.ready(true); } }, // 处理 DOM 准备就绪的情况 ready: function (wait) { // 如果 wait 参数为 true 且 readyWait 计数减 1 后为 0,或者 wait 参数不是 true 且 isReady 为 false if ((wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady)) { // 确保文档的 body 存在,以防 IE 浏览器过于激进(参考 ticket #5443)。 if (!document.body) { // 如果 body 不存在,则延迟调用 jQuery.ready 方法 return setTimeout(jQuery.ready, 1); } // 标记 DOM 已经准备好 jQuery.isReady = true; // 如果是常规的 DOM Ready 事件触发,计数减一,并在需要时等待 if (wait !== true && --jQuery.readyWait > 0) { return; // 如果还有其他等待的事件,则直接返回 } // 如果有绑定的函数,需要执行这些函数 readyList.fireWith(document, [jQuery]); // 触发任何已绑定的 ready 事件 if (jQuery.fn.trigger) { // 触发 "ready" 事件并移除该事件的所有处理程序 jQuery(document).trigger("ready").off("ready"); } } }, bindReady: function () { // 如果 readyList 已经被定义,则直接返回,避免重复绑定 if (readyList) { return; } // 创建一个新的回调列表,用于存储准备就绪的事件,设置为 "once memory" 选项 readyList = jQuery.Callbacks("once memory"); // 捕获 $(document).ready() 在浏览器事件已发生后调用的情况 if (document.readyState === "complete") { // 以异步方式处理,以允许脚本有机会延迟 ready 状态 return setTimeout(jQuery.ready, 1); } // Mozilla、Opera 和 Webkit Nightlies 当前支持这个事件 if (document.addEventListener) { // 使用方便的事件回调处理 DOMContentLoaded 事件 document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); // 为 window.onload 提供备用处理,这个处理会始终有效 window.addEventListener("load", jQuery.ready, false); // 如果使用的是 IE 的事件模型 } else if (document.attachEvent) { // 确保在 onload 之前触发, // 尽管可能稍晚,但对于 iframe 来说也很安全 document.attachEvent("onreadystatechange", DOMContentLoaded); // 为 window.onload 提供备用处理,这个处理会始终有效 window.attachEvent("onload", jQuery.ready); // 如果是在 IE 并且不是一个框架 // 持续检查文档是否已准备好 var toplevel = false; // 尝试判断当前窗口是否为顶层窗口 try { toplevel = window.frameElement == null; // 检查 frameElement 是否为空 } catch (e) { } // 如果文档支持 doScroll 并且是顶层窗口,就进行滚动检查 if (document.documentElement.doScroll && toplevel) { doScrollCheck(); // 调用 doScrollCheck 方法来检查文档准备状态 } } }, // 参见 test/unit/core.js 文件以获取关于 isFunction 的详细信息。 // 从版本 1.3 开始,不支持 DOM 方法和诸如 alert 的函数。 // 在 IE 中,它们会返回 false (#2968)。 isFunction: function (obj) { // 检查给定对象的类型是否为 "function" return jQuery.type(obj) === "function"; }, // 判断是否为数组的函数。优先使用原生 Array.isArray 方法,如果不存在,则使用自定义实现。 isArray: Array.isArray || function (obj) { // 检查给定对象的类型是否为 "array" return jQuery.type(obj) === "array"; }, // 判断给定对象是否为窗口对象的函数 isWindow: function (obj) { // 确保对象不为 null,并且确实是一个窗口对象 return obj != null && obj == obj.window; }, // 判断给定对象是否为数字的函数 isNumeric: function (obj) { // 检查将对象解析为浮点数后,是否不是 NaN 并且是有限数值 return !isNaN(parseFloat(obj)) && isFinite(obj); }, // 用于确定给定对象类型的通用函数 type: function (obj) { // 如果对象为 null,返回其字符串表示 return obj == null ? String(obj) : // 根据对象的内部类型标签(通过 toString.call 获取)返回相应的类型名称, // 如果找不到对应的类型,则返回 "object" class2type[toString.call(obj)] || "object"; }, isPlainObject: function (obj) { // 必须是一个对象。 // 由于 IE 的原因,我们还必须检查构造函数属性的存在性。 // 确保 DOM 节点和窗口对象不会通过该检查。 if (!obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) { // 如果 obj 为 null、不是对象类型、是 DOM 节点或是窗口对象,则返回 false return false; } try { // 检查 obj 的构造函数: // 如果构造函数存在且不是本对象的属性,并且其原型上没有 isPrototypeOf 方法,则返回 false if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } } catch (e) { // 在某些宿主对象上,IE8 和 IE9 会抛出异常,捕获这些异常并返回 false return false; } // 自有属性首先被枚举,因此为了提高速度, // 如果最后一个 key 是自有属性,那么所有属性都是自有属性。 var key; for (key in obj) { // 遍历 obj 的所有属性 } // 如果最后一个 key 是 undefined,表示 obj 没有可枚举的属性; // 或者最后一个 key 是 obj 的自有属性,返回 true。 return key === undefined || hasOwn.call(obj, key); }, isEmptyObject: function (obj) { // 遍历 obj 的所有属性 for (var name in obj) { // 如果 obj 中有任何属性,返回 false,表示对象不为空 return false; } // 如果没有属性,则返回 true,表示对象为空 return true; }, error: function (msg) { // 抛出一个新的错误,错误信息为传入的 msg throw new Error(msg); }, parseJSON: function (data) { // 首先检查传入的数据是否为字符串且非空 if (typeof data !== "string" || !data) { return null; // 如果不是字符串或为空,返回 null } // 确保去掉首尾的空格(IE 无法处理) data = jQuery.trim(data); // 尝试使用原生 JSON 解析器进行解析 if (window.JSON && window.JSON.parse) { return window.JSON.parse(data); // 若支持 JSON 解析,直接使用它进行解析 } // 确保传入的数据是有效的 JSON // 逻辑借鉴自 http://json.org/json2.js if (rvalidchars.test(data.replace(rvalidescape, "@") .replace(rvalidtokens, "]") .replace(rvalidbraces, ""))) { // 如果数据有效,则利用 Function 构造函数执行并返回结果 return (new Function("return " + data))(); } // 如果数据无效,则调用 jQuery.error 抛出错误,包含具体的无效 JSON 数据 jQuery.error("Invalid JSON: " + data); }, // 跨浏览器 XML 解析 parseXML: function (data) { // 首先检查传入的数据是否为字符串且非空 if (typeof data !== "string" || !data) { return null; // 如果不是字符串或为空,返回 null } var xml, tmp; // 声明变量用于保存解析后的 XML 文档和临时对象 try { if (window.DOMParser) { // 标准浏览器支持 tmp = new DOMParser(); // 创建一个 DOMParser 实例 xml = tmp.parseFromString(data, "text/xml"); // 使用 DOMParser 解析字符串为 XML } else { // IE 浏览器支持 xml = new ActiveXObject("Microsoft.XMLDOM"); // 创建 ActiveXObject 实例 xml.async = "false"; // 设置异步为 false,以同步解析 xml.loadXML(data); // 加载 XML 字符串 } } catch (e) { xml = undefined; // 捕获异常并将 xml 设置为 undefined } // 检查解析结果是否有效 if (!xml || !xml.documentElement || xml.getElementsByTagName("parsererror").length) { // 如果没有根元素或者存在解析错误,则抛出错误 jQuery.error("Invalid XML: " + data); } return xml; // 返回解析后的 XML 文档 }, // 空函数,用于占位或回调 noop: function () { }, // 在全局上下文中评估脚本 // 根据 Jim Driscoll 的发现进行的解决方案 // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function (data) { // 检查数据是否存在且不为空(使用正则表达式 rnotwhite 测试) if (data && rnotwhite.test(data)) { // 在 Internet Explorer 中使用 execScript // 使用匿名函数以确保上下文为 window,而不是 Firefox 中的 jQuery (window.execScript || function (data) { // 调用 window 上下文中的 eval 方法,以评估传入的数据 window["eval"].call(window, data); })(data); // 执行脚本 } }, // 将带有连字符的字符串转换为驼峰命名法;在 css 和 data 模块中使用 // 微软忘记了处理他们的供应商前缀 (#9572) camelCase: function (string) { // 用正则表达式将 'ms-' 前缀添加到字符串中,并将连字符后的字母转为大写 return string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase); }, // 检查元素的节点名称是否与给定名称匹配(不区分大小写) nodeName: function (elem, name) { // 如果元素存在,且其节点名称(大写形式)与给定名称(大写形式)匹配,则返回 true return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // args 仅供内部使用 each: function (object, callback, args) { var name, i = 0, length = object.length, // 获取对象的长度 isObj = length === undefined || jQuery.isFunction(object); // 判断是否为对象或函数 // 如果提供了 args 参数 if (args) { // 判断是否为对象,如果是对象(或函数) if (isObj) { // 遍历对象的每个属性 for (name in object) { // 使用 apply 调用回调函数,传入当前对象和参数数组 if (callback.apply(object[name], args) === false) { break; // 如果回调返回 false,则停止遍历 } } } else { // 否则,遍历数组 for (; i < length;) { // 同样使用 apply 调用回调函数,传入当前元素和参数数组 if (callback.apply(object[i++], args) === false) { break; // 停止遍历条件同上 } } } // 处理没有 args 参数的特殊情况,通常是最常见的用法 } else { // 判断是否为对象 if (isObj) { // 遍历对象的每个属性 for (name in object) { // 使用 call 调用回调函数,传入当前属性名和属性值 if (callback.call(object[name], name, object[name]) === false) { break; // 停止遍历条件同上 } } } else { // 否则,遍历数组 for (; i < length;) { // 使用 call 调用回调函数,传入当前索引和元素 if (callback.call(object[i], i, object[i++]) === false) { break; // 停止遍历条件同上 } } } } return object; // 返回原始对象,方便链式调用 }, // 使用原生的 String.trim 函数(如果可用) trim: trim ? function (text) { // 如果文本为 null,则返回空字符串 return text == null ? "" : // 调用原生的 trim 方法来去除字符串两端的空白 trim.call(text); } : // 否则使用自定义的去空白功能 function (text) { // 如果文本为 null,则返回空字符串 return text == null ? "" : // 将文本转换为字符串并使用正则表达式去除两端的空白 text.toString().replace(trimLeft, "").replace(trimRight, ""); }, // results 仅供内部使用 makeArray: function (array, results) { var ret = results || []; // 如果提供了 results,使用它;否则创建一个新的数组 if (array != null) { // 检查 array 是否不为 null // 获取 array 的类型 var type = jQuery.type(array); // 如果 array 没有 length 属性,或者是字符串、函数、正则表达式或窗口对象 if (array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow(array)) { // 将 array 直接推入结果数组 push.call(ret, array); } else { // 否则,合并 array 的元素到结果数组中 jQuery.merge(ret, array); } } return ret; // 返回处理后的结果数组 }, // 在数组中查找指定元素的位置 inArray: function (elem, array, i) { var len; // 检查传入的数组是否存在 if (array) { // 如果支持原生的 indexOf 方法 if (indexOf) { // 使用原生 indexOf 方法查找元素并返回索引 return indexOf.call(array, elem, i); } len = array.length; // 获取数组长度 // 根据传入的索引 i 进行处理,支持负索引 i = i ? i < 0 ? Math.max(0, len + i) : i : 0; // 遍历数组,查找与 elem 相等的元素 for (; i < len; i++) { // 跳过稀疏数组(即未定义的索引) if (i in array && array[i] === elem) { return i; // 找到元素,返回其索引 } } } return -1; // 没有找到元素,返回 -1 }, // 合并两个数组 merge: function (first, second) { var i = first.length, // 获取第一个数组的当前长度 j = 0; // 初始化第二个数组的索引 // 检查第二个参数是否为数组(即拥有 length 属性的对象) if (typeof second.length === "number") { // 遍历 second 数组,将其元素添加到 first 数组中 for (var l = second.length; j < l; j++) { first[i++] = second[j]; // 将 second 的元素推入 first } } else { // 如果 second 不是一个有效的数组,使用 while 循环逐个添加元素 while (second[j] !== undefined) { first[i++] = second[j++]; // 添加元素直至遇到 undefined } } first.length = i; // 更新 first 数组的长度 return first; // 返回合并后的数组 }, // 定义一个 grep 函数,用于过滤数组中的元素 grep: function (elems, callback, inv) { var ret = [], // 初始化一个空数组,用于保存符合条件的元素 retVal; // 用于存储回调函数的返回值 inv = !!inv; // 将 inv 转换为布尔值,确保其为 true 或 false // 遍历传入的数组 elems for (var i = 0, length = elems.length; i < length; i++) { // 调用回调函数,传入当前元素及其索引,并将返回值转换为布尔值 retVal = !!callback(elems[i], i); // 根据 inv 的值决定是否加入 ret 数组 if (inv !== retVal) { ret.push(elems[i]); // 如果 inv 与 retVal 不同,则将当前元素添加到结果数组中 } } return ret; // 返回符合条件的元素数组 }, // arg 仅供内部使用 map: function (elems, callback, arg) { var value, // 用于存储回调函数返回的值 key, // 用于遍历对象时的键 ret = [], // 初始化一个空数组,用于保存结果 i = 0, // 循环计数器 length = elems.length, // 获取传入元素的长度 // 检查 elems 是否为数组或 jQuery 对象 isArray = elems instanceof jQuery || (length !== undefined && typeof length === "number" && ((length > 0 && elems[0] && elems[length - 1]) || length === 0 || jQuery.isArray(elems))); // 如果 elems 是数组或类数组对象 if (isArray) { // 遍历数组,调用回调函数,并处理每个元素 for (; i < length; i++) { value = callback(elems[i], i, arg); // 调用回调函数,传入当前元素、索引和 arg if (value != null) { // 检查返回值是否不为 null 或 undefined ret[ret.length] = value; // 将有效的返回值添加到结果数组中 } } // 如果 elems 是对象 } else { // 遍历对象的每个键 for (key in elems) { value = callback(elems[key], key, arg); // 调用回调函数,传入当前属性值、键和 arg if (value != null) { // 检查返回值是否不为 null 或 undefined ret[ret.length] = value; // 将有效的返回值添加到结果数组中 } } } // 扁平化任何嵌套数组,并返回结果数组 return ret.concat.apply([], ret); }, // 全局唯一标识符(GUID)计数器,用于对象 guid: 1, // 将函数绑定到指定的上下文,且可选地部分应用参数 proxy: function (fn, context) { // 检查 context 是否为字符串,如果是,则将 fn 的相应方法作为目标函数 if (typeof context === "string") { var tmp = fn[context]; // 获取 fn 对象中 context 属性对应的方法 context = fn; // 将上下文设置为 fn fn = tmp; // 将 fn 更新为该属性方法 } // 快速检查目标函数是否可以调用,若不可调用则返回 undefined if (!jQuery.isFunction(fn)) { return undefined; // 如果 fn 不是函数,返回 undefined } // 模拟 bind 方法,创建一个代理函数 var args = slice.call(arguments, 2), // 获取除了前两个参数外的其他所有参数 proxy = function () { // 在代理函数中调用原函数,并将上下文和参数传入 return fn.apply(context, args.concat(slice.call(arguments))); }; // 将代理函数的 guid 设置为原始函数的 guid,这样可以通过该 guid 移除代理 proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; // 确保每个函数都有一个唯一的 guid return proxy; // 返回代理函数 }, // 多功能方法,用于获取和设置集合中的值 // 值可以在函数的情况下选择性地被执行 access: function (elems, fn, key, value, chainable, emptyGet, pass) { var exec, // 用于判断是否执行函数值 bulk = key == null, // 判断是否为批量操作,如果 key 为 null 则为批量操作 i = 0, length = elems.length; // 获取元素集合的长度 // 设置多个值 if (key && typeof key === "object") { for (i in key) { // 对于对象中的每个键,递归调用 access 方法设置值 jQuery.access(elems, fn, i, key[i], 1, emptyGet, value); } chainable = 1; // 设置链式调用标志 // 设置单个值 } else if (value !== undefined) { // 如果没有传递 pass 参数且 value 是一个函数,则执行该函数 exec = pass === undefined && jQuery.isFunction(value); if (bulk) { // 批量操作仅在执行函数值时进行迭代 if (exec) { exec = fn; // 保存原始的函数 // 将 fn 替换为一个新的函数,该函数将在每个元素上调用 exec fn = function (elem, key, value) { return exec.call(jQuery(elem), value); }; // 否则,对整个集合直接运行 } else { fn.call(elems, value); // 通过 fn 函数设置所有元素的值 fn = null; // 将 fn 设为 null,表示不再需要它 } } if (fn) { // 遍历 elements 数组,为每个元素调用 fn 函数,并传入相应的参数 for (; i < length; i++) { fn(elems[i], key, exec ? value.call(elems[i], i, fn(elems[i], key)) : value, pass); } } chainable = 1; // 设置链式调用标志 } // 返回元素集合或获取值 return chainable ? elems : // 如果是链式调用,返回 elems bulk ? fn.call(elems) : // 如果是批量操作,则获取所有元素的值 length ? fn(elems[0], key) : emptyGet; // 获取第一个元素的值,或者返回 emptyGet }, now: function () { // 返回当前时间的时间戳(自1970年1月1日以来的毫秒数) return (new Date()).getTime(); }, // 使用 jQuery.browser 已被认为不推荐使用。 // 更多详细信息请参见 http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function (ua) { // 将用户代理字符串转为小写字母 ua = ua.toLowerCase(); // 匹配用户代理中的浏览器信息 var match = rwebkit.exec(ua) || // 检查是否为 WebKit 引擎 ropera.exec(ua) || // 检查是否为 Opera 浏览器 rmsie.exec(ua) || // 检查是否为 Internet Explorer ua.indexOf("compatible") < 0 && rmozilla.exec(ua) || // 检查是否为 Mozilla 浏览器 []; // 如果没有匹配项,则返回空数组 // 返回一个包含浏览器名称和版本号的对象 return {browser: match[1] || "", version: match[2] || "0"}; }, sub: function () { // 定义一个新的 jQuery 子类 function jQuerySub(selector, context) { // 返回一个新的 jQuerySub 实例 return new jQuerySub.fn.init(selector, context); } // 扩展 jQuerySub,使其继承 jQuery 的属性和方法 jQuery.extend(true, jQuerySub, this); jQuerySub.superclass = this; // 保存父类引用 jQuerySub.fn = jQuerySub.prototype = this(); // 设置原型链 // 设置构造函数 jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; // 继承子类方法 // 初始化方法,处理上下文参数 jQuerySub.fn.init = function init(selector, context) { // 如果 context 是 jQuery 实例且不是 jQuerySub 实例,则转换为 jQuerySub 实例 if (context && context instanceof jQuery && !(context instanceof jQuerySub)) { context = jQuerySub(context); } // 调用父类的初始化方法 return jQuery.fn.init.call(this, selector, context, rootjQuerySub); }; // 设置初始化原型 jQuerySub.fn.init.prototype = jQuerySub.fn; // 创建一个新的 jQuerySub 实例以作为根 var rootjQuerySub = jQuerySub(document); return jQuerySub; // 返回新的 jQuerySub 类 }, browser: {} // 定义一个空对象以存放浏览器相关的信息 }); // 填充 class2type 映射表 jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { // 将 JavaScript 数据类型的字符串映射到对应的小写名称 class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); // 匹配用户代理信息 browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { // 如果找到浏览器类型,则将其标记为 true,并设置版本号 jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } // 已被弃用,使用 jQuery.browser.webkit 替代 if ( jQuery.browser.webkit ) { // 如果是 WebKit 浏览器,则标记为 Safari jQuery.browser.safari = true; } // 对于 IE,不会将非断行空格与 \s 匹配 if ( rnotwhite.test( "\xA0" ) ) { // 设置左侧和右侧的 trim 正则表达式,以去除空格和非断行空格 trimLeft = /^[\s\xA0]+/; // 匹配开头的空格和非断行空格 trimRight = /[\s\xA0]+$/; // 匹配结尾的空格和非断行空格 } // 所有 jQuery 对象都应指向这个根 jQuery 实例 rootjQuery = jQuery(document); // 文档就绪方法的清理函数 if ( document.addEventListener ) { // 如果支持 addEventListener 方法 DOMContentLoaded = function() { // 移除 DOMContentLoaded 事件监听 document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // 调用 jQuery 的就绪方法 jQuery.ready(); }; } else if ( document.attachEvent ) { // 如果支持 attachEvent 方法(主要针对 IE) DOMContentLoaded = function() { // 确保文档状态为完整(complete),以避免 IE 过于积极的操作 if ( document.readyState === "complete" ) { // 移除 onreadystatechange 事件监听 document.detachEvent( "onreadystatechange", DOMContentLoaded ); // 调用 jQuery 的就绪方法 jQuery.ready(); } }; } // 检查 DOM 是否准备就绪,专门针对 Internet Explorer function doScrollCheck() { if ( jQuery.isReady ) { // 如果 jQuery 已经准备好,则直接返回 return; } try { // 如果使用的是 IE,采用 Diego Perini 的技巧来检查内容加载 // 参考链接: http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch(e) { // 如果出现异常,设置延时调用 doScrollCheck 函数,以继续检查 setTimeout( doScrollCheck, 1 ); return; } // 一旦 DOM 加载完毕,执行所有等待中的函数 jQuery.ready(); } // 返回 jQuery 对象(这部分通常是立即调用的函数表达式的一部分) return jQuery; })(); // 字符串到对象的标志格式缓存 var flagsCache = {}; // 将字符串格式的标志转换为对象格式,并存储在缓存中 function createFlags( flags ) { var object = flagsCache[ flags ] = {}, // 创建一个新的对象并存入缓存 i, length; // 将传入的字符串按空格分割成数组 flags = flags.split( /\s+/ ); for ( i = 0, length = flags.length; i < length; i++ ) { // 遍历每个标志,将其作为对象的属性,值为 true object[ flags[i] ] = true; } return object; // 返回转换后的对象 } /* * Create a callback list using the following parameters: * * flags: an optional list of space-separated flags that will change how * the callback list behaves * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible flags: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( flags ) { // 将字符串格式的标志转换为对象格式 // 首先在缓存中检查 flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // 实际的回调列表 list = [], // 用于重复调用的堆栈 stack = [], // 最后一次触发的值(用于不可遗忘列表) memory, // 标记以知晓列表是否已经触发 fired, // 标记以知晓列表是否正在触发中 firing, // 第一个要触发的回调(由 add 和 fireWith 内部使用) firingStart, // 触发时循环的结束 firingLength, // 当前正在执行的回调索引(可被 remove 修改) firingIndex, // 向列表添加一个或多个回调的函数 add = function( args ) { var i, length, elem, type, actual; // 遍历传入的参数数组 for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; // 获取当前元素 type = jQuery.type( elem ); // 检查元素类型 if ( type === "array" ) { // 如果元素是数组,递归调用 add 函数 add( elem ); } else if ( type === "function" ) { // 如果元素是函数且不是唯一模式下,且回调不在列表中,则添加到列表 if ( !flags.unique || !self.has( elem ) ) { list.push( elem ); // 将函数添加到回调列表中 } } } }; // 执行回调函数 fire = function( context, args ) { args = args || []; // 确保 args 是一个数组,如果没有传入则初始化为空数组 memory = !flags.memory || [ context, args ]; // 根据 flags.memory 标志决定是否记录触发的上下文和参数 fired = true; // 标记为已触发 firing = true; // 标记为正在触发 firingIndex = firingStart || 0; // 初始化当前索引,firingStart 为 0 或之前保存的值 firingStart = 0; // 重置触发开始的位置 firingLength = list.length; // 获取回调列表的长度 // 遍历回调列表并执行每个函数 for ( ; list && firingIndex < firingLength; firingIndex++ ) { // 执行当前索引的回调函数,并传递上下文和参数 if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { memory = true; // 如果返回 false 且设置了 stopOnFalse,标记为中止 break; // 中止循环 } } firing = false; // 结束触发状态 if ( list ) { // 如果回调列表存在 if ( !flags.once ) { // 如果未设置一次性标志 if ( stack && stack.length ) { // 检查堆栈是否存在且有元素 memory = stack.shift(); // 从堆栈中取出下一个要触发的记忆(上下文和参数) self.fireWith( memory[ 0 ], memory[ 1 ] ); // 以新的上下文和参数再次触发回调 } } else if ( memory === true ) { // 如果 memory 为 true,表示触发完成 self.disable(); // 禁用回调列表 } else { list = []; // 在一次性情况下清空回调列表 } } }, // 实际的回调对象 self = { // 添加一个回调或一组回调到列表中 add: function() { if ( list ) { // 检查回调列表是否存在 var length = list.length; // 获取当前回调列表的长度 add( arguments ); // 调用 add 函数,将传入的参数添加到回调列表 // 我们需要将回调添加到当前正在触发的批次吗? if ( firing ) { firingLength = list.length; // 如果正在触发,更新当前回调列表的长度 // 如果有记忆值,且当前没有触发, // 则应该立即调用回调,除非之前的触发被中止(stopOnFalse) } else if ( memory && memory !== true ) { firingStart = length; // 设置触发开始的位置为当前列表的长度 fire( memory[ 0 ], memory[ 1 ] ); // 立即触发回调,使用记忆中的上下文和参数 } } return this; // 返回当前对象,以支持链式调用 }, // 从列表中移除一个回调 remove: function() { if ( list ) { // 检查回调列表是否存在 var args = arguments, // 获取传入的参数 argIndex = 0, // 当前参数索引 argLength = args.length; // 参数的总长度 // 遍历所有传入的参数 for ( ; argIndex < argLength ; argIndex++ ) { // 对于每个参数,遍历回调列表 for ( var i = 0; i < list.length; i++ ) { // 如果当前参数与回调列表中的某个回调相等 if ( args[ argIndex ] === list[ i ] ) { // 处理 firingIndex 和 firingLength 的更新 if ( firing ) { // 当前正在触发,并且要移除的回调在触发列表之前 if ( i <= firingLength ) { firingLength--; // 更新触发长度 if ( i <= firingIndex ) { firingIndex--; // 更新触发索引 } } } // 移除该元素 list.splice( i--, 1 ); // 从列表中移除元素,并将索引减一以调整循环 // 如果设置了唯一性标志,那么只需移除一次 if ( flags.unique ) { break; // 跳出当前循环,不再检查其他相同的回调 } } } } } return this; // 返回当前对象,以支持链式调用 }, // 控制特定回调是否在列表中 has: function( fn ) { if ( list ) { // 检查回调列表是否存在 var i = 0, length = list.length; // 获取列表的长度 // 遍历回调列表 for ( ; i < length; i++ ) { // 如果找到与给定回调相同的项,则返回 true if ( fn === list[ i ] ) { return true; } } } return false; // 如果没有找到,返回 false }, // 从列表中移除所有回调 empty: function() { list = []; // 将列表清空 return this; // 返回当前对象,以支持链式调用 }, // 使列表不再执行任何操作 disable: function() { list = stack = memory = undefined; // 将列表、栈和内存设为 undefined return this; // 返回当前对象,以支持链式调用 }, // 检查是否已禁用 disabled: function() { return !list; // 如果列表不存在,则返回 true }, // 锁定列表,使其处于当前状态 lock: function() { stack = undefined; // 清空栈 // 如果没有内存或内存为 true,则禁用列表 if ( !memory || memory === true ) { self.disable(); // 调用 disable 方法 } return this; // 返回当前对象,以支持链式调用 }, // 检查列表是否被锁定 locked: function() { return !stack; // 如果栈不存在,则返回 true }, // 使用给定的上下文和参数调用所有回调 fireWith: function( context, args ) { if ( stack ) { // 检查栈是否存在 if ( firing ) { // 检查是否正在执行回调 // 如果当前正在执行且未设置一次性标志,推入新的上下文和参数到栈中 if ( !flags.once ) { stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { // 如果未设置一次性标志且没有内存记录 fire( context, args ); // 立即调用回调函数 } } return this; // 返回当前对象,以支持链式调用 }, // 使用给定的参数调用所有回调 fire: function() { self.fireWith( this, arguments ); // 调用 fireWith 方法,传入当前上下文和参数 return this; // 返回当前对象,以支持链式调用 }, // 检查回调是否至少已被调用过一次 fired: function() { return !!fired; // 将 fired 转换为布尔值返回,表明回调是否已被调用 } }; return self; }; // 静态引用 slice 方法,用于数组切片 var sliceDeferred = [].slice; jQuery.extend({ // 定义 Deferred 函数,用于创建一个新的延迟对象 Deferred: function( func ) { // 创建三个回调列表:成功、失败和进度 var doneList = jQuery.Callbacks("once memory"), // 成功回调列表,只能被调用一次,并保留内存 failList = jQuery.Callbacks("once memory"), // 失败回调列表,同样只调用一次并保留内存 progressList = jQuery.Callbacks("memory"), // 进度回调列表,可以多次调用并保留内存 state = "pending", // 初始状态为“待处理” lists = { resolve: doneList, // 将成功列表绑定到 resolve reject: failList, // 将失败列表绑定到 reject notify: progressList // 将进度列表绑定到 notify }, promise = { // 定义 promise 对象 done: doneList.add, // 添加成功回调的方法 fail: failList.add, // 添加失败回调的方法 progress: progressList.add, // 添加进度回调的方法 state: function () { // 返回当前状态的方法 return state; // 返回状态 }, // 已废弃的方法,检查是否已经解决 isResolved: doneList.fired, // 检查成功回调是否已被调用 isRejected: failList.fired, // 检查失败回调是否已被调用 then: function (doneCallbacks, failCallbacks, progressCallbacks) { // 链式调用,将相应的回调添加到 deferred 对象 deferred.done(doneCallbacks).fail(failCallbacks).progress(progressCallbacks); return this; // 返回当前对象,以支持链式调用 }, always: function () { // 无论成功或失败都执行的回调 deferred.done.apply(deferred, arguments).fail.apply(deferred, arguments); return this; // 返回当前对象,以支持链式调用 }, pipe: function (fnDone, fnFail, fnProgress) { // 创建一个新的延迟对象,允许链式调用 return jQuery.Deferred(function (newDefer) { // 遍历每个处理程序(成功、失败和进度) jQuery.each({ done: [fnDone, "resolve"], // 成功回调及其对应的操作 fail: [fnFail, "reject"], // 失败回调及其对应的操作 progress: [fnProgress, "notify"] // 进度回调及其对应的操作 }, function (handler, data) { var fn = data[0], // 获取回调函数 action = data[1], // 获取对应的操作 returned; // 用于存储返回值 // 检查 fn 是否为一个有效的函数 if (jQuery.isFunction(fn)) { // 为当前的 deferred 添加相应的回调处理 deferred[handler](function () { returned = fn.apply(this, arguments); // 调用该函数并获取返回值 // 检查返回值是否是一个具有 promise 方法的对象 if (returned && jQuery.isFunction(returned.promise)) { // 若是,则将新的 deferred 的 resolve/reject/notify 方法与返回的 promise 绑定 returned.promise().then(newDefer.resolve, newDefer.reject, newDefer.notify); } else { // 否则,直接将返回值传递到新的 deferred newDefer[action + "With"](this === deferred ? newDefer : this, [returned]); } }); } else { // 如果 fn 不是函数,则直接将新的 deferred 的操作与原 deferred 的操作绑定 deferred[handler](newDefer[action]); } }); }).promise(); // 返回新的 promise 对象 }, // 获取当前 deferred 的 promise // 如果提供了 obj,则将 promise 方法添加到该对象上 promise: function (obj) { if (obj == null) { obj = promise; // 如果没有提供对象,则使用当前的 promise } else { // 将 promise 中的方法复制到提供的对象中 for (var key in promise) { obj[key] = promise[key]; } } return obj; // 返回最终的对象 } }, deferred = promise.promise({}), key; // 遍历 lists 对象中的每个 key for (key in lists) { // 将 lists 中相应的 fire 方法赋值给 deferred 对象的对应属性 deferred[key] = lists[key].fire; // 将 lists 中的 fireWith 方法赋值给 deferred 对象的对应属性,允许带上下文和参数调用 deferred[key + "With"] = lists[key].fireWith; } // 处理状态变化 deferred.done(function () { // 当执行完所有成功回调后,将状态设置为 "resolved" state = "resolved"; }, failList.disable, progressList.lock).fail(function () { // 当执行失败回调时,将状态设置为 "rejected" state = "rejected"; }, doneList.disable, progressList.lock); // 如果提供了 func 函数,则调用它,并将 deferred 作为上下文传入 if (func) { func.call(deferred, deferred); } // 返回 deferred 对象,表示所有操作已完成 return deferred; }, // Deferred 辅助函数 when: function( firstParam ) { // 将传入的参数转换为数组形式,取出所有参数 var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, // 参数长度 pValues = new Array( length ), // 用于存储进度值的数组 count = length, // 计数器,用于跟踪已解析的 promise 数量 pCount = length, // 另一个计数器,可能用于未来扩展 deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? // 如果只有一个参数且它是一个可执行 promise 的对象,则使用这个对象;否则创建新的 Deferred 对象 firstParam : jQuery.Deferred(), promise = deferred.promise(); // 获取与 deferred 相关联的 promise 对象 // 定义解析函数,用于处理成功回调 function resolveFunc( i ) { return function( value ) { // 将对应索引的参数值设置为传入的 value,如果有多个参数则存储为数组 args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; // 减少计数器,当所有 promise 都已解决时,调用 resolve if ( !( --count ) ) { deferred.resolveWith( deferred, args ); // 触发 deferred 的 resolved 状态,并传入所有结果 } }; } // 定义进度处理函数,用于处理进度通知 function progressFunc( i ) { return function( value ) { // 将进度值存储在 pValues 数组中 pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; // 通知 promise 的进度更新 deferred.notifyWith( promise, pValues ); // 发送当前进度值给观察者 }; } if ( length > 1 ) { // 如果参数个数大于1 for ( ; i < length; i++ ) { // 遍历所有参数 // 检查当前参数是否为一个有 promise 方法的对象 if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { // 调用当前参数的 promise 方法,并处理成功和失败的回调 // resolveFunc(i) 用于成功时的处理,deferred.reject 用于失败时的处理,progressFunc(i) 用于进度更新的处理 args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { // 如果当前参数不是一个有效的 promise,则计数器减一 --count; } } // 如果 count 计数器为0,表示所有的 promise 都已解决 if ( !count ) { // 将 args 中的所有结果传递给 deferred 的 resolved 状态,完成整个过程 deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { // 如果只有一个参数且它不是最初传入的 firstParam // 将 firstParam 的值作为数组传递给 deferred 的 resolved 状态 deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } // 最后返回与 deferred 相关联的 promise 对象 return promise; } }); jQuery.support = (function() { // 定义 jQuery.support 作为一个立即执行函数表达式 (IIFE) var support, // 用于存储支持的特性 all, // 用于存储 div 中的所有元素 a, // 存储第一个 元素 select, // 将来可能用于存储 元素 fragment, // 将来可能用于存储文档片段 tds, // 将来可能用于存储 元素 events, // 将来可能用于存储事件支持情况 eventName, // 将来可能用于存储事件名称 i, // 循环计数器 isSupported, // 用于指示某些特性的支持状态 div = document.createElement("div"), // 创建一个新的 div 元素 documentElement = document.documentElement; // 获取文档根元素 // 进行初步测试 div.setAttribute("className", "t"); // 设置 div 的 className 属性(IE 特有) // 设置 div 的 innerHTML,包括一些元素和属性 div.innerHTML = "
a"; all = div.getElementsByTagName("*"); // 获取 div 中的所有元素 a = div.getElementsByTagName("a")[0]; // 获取 div 中的第一个 元素 // 如果无法获取基本的测试支持,则返回一个空对象 if ( !all || !all.length || !a ) { return {}; } // 第一批支持测试 select = document.createElement("select"); // 创建一个 中添加一个 元素的 style 属性中是否包含 "top" // 确保 URL 不会被修改 // (IE 默认会对其进行规范化) hrefNormalized: (a.getAttribute("href") === "/a"), // 检查 元素的 href 是否为指定的值 // 确保元素透明度存在 // (IE 使用 filter 代替) // 使用正则表达式来解决 WebKit 问题 opacity: /^0.55/.test(a.style.opacity), // 检查 元素的 opacity 样式是否为 0.55 // 验证样式浮动的存在 // (IE 使用 styleFloat 而不是 cssFloat) cssFloat: !!a.style.cssFloat, // 检查 元素的 cssFloat 是否存在并转换为布尔值 // 确保如果未指定复选框的值,则默认为 "on" // (WebKit 默认为 "") checkOn: (input.value === "on"), // 检查 input 元素的值是否为 "on" // 确保默认选中的选项具有有效的 selected 属性。 // (WebKit 默认情况下为 false,而 IE 也是如此,如果它在 optgroup 中) optSelected: opt.selected, // 检查选项是否被选中 // 测试 camelCase 类的 setAttribute 是否有效。如果有效,则在进行 get/setAttribute 时需要 attrFixes(适用于 IE6/7) getSetAttribute: div.className !== "t", // 检查 div 的 className 是否不等于 "t" // 测试表单上的 enctype 支持 (#6743) enctype: !!document.createElement("form").enctype, // 检查新的表单元素是否具有 enctype 属性并转换为布尔值 // 确保克隆一个 HTML5 元素不会导致问题 // 当 outerHTML 未定义时,这仍然有效 html5Clone: document.createElement("nav").cloneNode(true).outerHTML !== "<:nav>", // 检查克隆的