|
|
|
@ -13,7 +13,872 @@
|
|
|
|
|
* jQuery的立即执行函数表达式(IIFE),用于创建一个封闭的作用域,避免全局变量污染。
|
|
|
|
|
* 它接受两个参数:global(全局对象,通常为window)和factory(一个函数,用于定义jQuery)。
|
|
|
|
|
*/
|
|
|
|
|
(function( global, factory ) {
|
|
|
|
|
|
|
|
|
|
// 如果在CommonJS环境中(如Node.js),并且module.exports存在
|
|
|
|
|
if ( typeof module === "object" && typeof module.exports === "object" ) {
|
|
|
|
|
|
|
|
|
|
// 如果全局对象有document属性,则直接导出jQuery
|
|
|
|
|
// 否则,导出一个函数,该函数在被调用时会检查是否存在document
|
|
|
|
|
module.exports = global.document ?
|
|
|
|
|
factory( global, true ) :
|
|
|
|
|
function( w ) {
|
|
|
|
|
// 如果没有document,则抛出错误
|
|
|
|
|
if ( !w.document ) {
|
|
|
|
|
throw new Error( "jQuery需要一个包含document的window对象" );
|
|
|
|
|
}
|
|
|
|
|
// 否则,调用factory函数创建jQuery
|
|
|
|
|
return factory( w );
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// 在非CommonJS环境中,直接调用factory函数
|
|
|
|
|
factory( global );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
|
|
|
|
|
|
|
|
|
|
// 用于存储已删除的ID的数组
|
|
|
|
|
var deletedIds = [];
|
|
|
|
|
|
|
|
|
|
// 获取全局的document对象
|
|
|
|
|
var document = window.document;
|
|
|
|
|
|
|
|
|
|
// 从deletedIds数组借用一些数组方法,用于后续操作
|
|
|
|
|
var slice = deletedIds.slice;
|
|
|
|
|
var concat = deletedIds.concat;
|
|
|
|
|
var push = deletedIds.push;
|
|
|
|
|
var indexOf = deletedIds.indexOf;
|
|
|
|
|
|
|
|
|
|
// 一个空对象,用于存储类名到类型的映射
|
|
|
|
|
var class2type = {};
|
|
|
|
|
|
|
|
|
|
// 获取class2type对象的toString方法
|
|
|
|
|
var toString = class2type.toString;
|
|
|
|
|
|
|
|
|
|
// 获取class2type对象的hasOwnProperty方法
|
|
|
|
|
var hasOwn = class2type.hasOwnProperty;
|
|
|
|
|
|
|
|
|
|
// 一个空对象,用于存储浏览器支持的特性
|
|
|
|
|
var support = {};
|
|
|
|
|
|
|
|
|
|
// jQuery的版本号
|
|
|
|
|
var version = "1.12.4";
|
|
|
|
|
|
|
|
|
|
// 定义jQuery对象,它实际上是init构造函数的增强版
|
|
|
|
|
var jQuery = function( selector, context ) {
|
|
|
|
|
// 如果直接调用jQuery而没有new,这里会返回一个新的jQuery.fn.init实例
|
|
|
|
|
return new jQuery.fn.init( selector, context );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 匹配并去除字符串开头和结尾的空白字符(包括BOM和NBSP)
|
|
|
|
|
var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
|
|
|
|
|
|
|
|
|
// 匹配以"-ms-"开头的字符串
|
|
|
|
|
var rmsPrefix = /^-ms-/;
|
|
|
|
|
|
|
|
|
|
// 匹配并替换字符串中的"-"后跟一个字母或数字的字符
|
|
|
|
|
var rdashAlpha = /-([\da-z])/gi;
|
|
|
|
|
|
|
|
|
|
// 用于将"-"后跟字母的字符串转换为驼峰命名法的回调函数
|
|
|
|
|
var fcamelCase = function( all, letter ) {
|
|
|
|
|
return letter.toUpperCase();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// jQuery的原型对象,包含所有实例方法
|
|
|
|
|
jQuery.fn = jQuery.prototype = {
|
|
|
|
|
|
|
|
|
|
// 当前jQuery的版本号
|
|
|
|
|
jquery: version,
|
|
|
|
|
|
|
|
|
|
// 构造函数指向jQuery本身
|
|
|
|
|
constructor: jQuery,
|
|
|
|
|
|
|
|
|
|
// 初始选择器字符串
|
|
|
|
|
selector: "",
|
|
|
|
|
|
|
|
|
|
// jQuery对象的默认长度为0
|
|
|
|
|
length: 0,
|
|
|
|
|
|
|
|
|
|
// 将jQuery对象转换为一个真正的数组
|
|
|
|
|
toArray: function() {
|
|
|
|
|
return slice.call( this );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取jQuery对象中的第N个元素,或者获取所有元素组成的数组
|
|
|
|
|
get: function( num ) {
|
|
|
|
|
return num != null ?
|
|
|
|
|
|
|
|
|
|
// 返回指定位置的元素
|
|
|
|
|
( num < 0 ? this[ num + this.length ] : this[ num ] ) :
|
|
|
|
|
|
|
|
|
|
// 返回所有元素组成的数组
|
|
|
|
|
slice.call( this );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 将一个元素数组推入到当前jQuery对象的栈中,并返回新的jQuery对象
|
|
|
|
|
pushStack: function( elems ) {
|
|
|
|
|
|
|
|
|
|
// 创建一个新的jQuery对象
|
|
|
|
|
var ret = jQuery.merge( this.constructor(), elems );
|
|
|
|
|
|
|
|
|
|
// 将旧的对象引用添加到新对象的prevObject属性上
|
|
|
|
|
ret.prevObject = this;
|
|
|
|
|
// 保持上下文的一致性
|
|
|
|
|
ret.context = this.context;
|
|
|
|
|
|
|
|
|
|
// 返回新的jQuery对象
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 对jQuery对象中的每个元素执行一次提供的回调函数
|
|
|
|
|
each: function( callback ) {
|
|
|
|
|
return jQuery.each( this, callback );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 将jQuery对象中的每个元素通过提供的回调函数映射到一个新数组中,并返回一个新的jQuery对象
|
|
|
|
|
map: function( callback ) {
|
|
|
|
|
return this.pushStack( jQuery.map( this, function( elem, i ) {
|
|
|
|
|
return callback.call( elem, i, elem );
|
|
|
|
|
}) );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 返回一个从当前位置开始,包含指定数量元素的新jQuery对象(如果参数是负数,则从末尾开始计数)
|
|
|
|
|
slice: function() {
|
|
|
|
|
return this.pushStack( slice.apply( this, arguments ) );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取jQuery对象中的第一个元素
|
|
|
|
|
first: function() {
|
|
|
|
|
return this.eq( 0 );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取jQuery对象中的最后一个元素
|
|
|
|
|
last: function() {
|
|
|
|
|
return this.eq( -1 );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取jQuery对象中指定位置的元素(如果索引是负数,则从末尾开始计数)
|
|
|
|
|
eq: function( i ) {
|
|
|
|
|
var len = this.length,
|
|
|
|
|
j = +i + ( i < 0 ? len : 0 );
|
|
|
|
|
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 结束当前操作,返回到上一个jQuery对象(如果有的话)
|
|
|
|
|
end: function() {
|
|
|
|
|
return this.prevObject || this.constructor();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 以下方法是从数组对象中借用来的,用于内部使用
|
|
|
|
|
push: push,
|
|
|
|
|
sort: deletedIds.sort,
|
|
|
|
|
splice: deletedIds.splice
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// jQuery.extend方法用于扩展jQuery对象本身或其原型对象
|
|
|
|
|
jQuery.extend = jQuery.fn.extend = function() {
|
|
|
|
|
var src, copyIsArray, copy, name, options, clone,
|
|
|
|
|
target = arguments[ 0 ] || {},
|
|
|
|
|
i = 1,
|
|
|
|
|
length = arguments.length,
|
|
|
|
|
deep = false;
|
|
|
|
|
|
|
|
|
|
// 处理深度复制的情况
|
|
|
|
|
if ( typeof target === "boolean" ) {
|
|
|
|
|
deep = target;
|
|
|
|
|
|
|
|
|
|
// 跳过布尔值和目标对象
|
|
|
|
|
target = arguments[ i ] || {};
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果目标不是对象或函数,则将其转换为对象
|
|
|
|
|
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
|
|
|
|
|
target = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果只有一个参数,则扩展jQuery本身
|
|
|
|
|
if ( i === length ) {
|
|
|
|
|
target = this;
|
|
|
|
|
i--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 遍历每一个要扩展的对象
|
|
|
|
|
for ( ; i < length; i++ ) {
|
|
|
|
|
|
|
|
|
|
// 只处理非null/undefined的值
|
|
|
|
|
if ( ( options = arguments[ i ] ) != null ) {
|
|
|
|
|
|
|
|
|
|
// 扩展基础对象
|
|
|
|
|
for ( name in options ) {
|
|
|
|
|
src = target[ name ];
|
|
|
|
|
copy = options[ name ];
|
|
|
|
|
|
|
|
|
|
// 防止无限循环
|
|
|
|
|
if ( target === copy ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// 深度复制的逻辑(略)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Recurse if we're merging plain objects or arrays
|
|
|
|
|
// 定义一个函数,用于扩展对象或合并对象
|
|
|
|
|
// deep 参数指示是否进行深度拷贝
|
|
|
|
|
// copy 是要合并到第一个对象中的对象或数组
|
|
|
|
|
// target 是被扩展的对象
|
|
|
|
|
var someFunction = function( deep, copy, target ) {
|
|
|
|
|
// 检查是否进行深度拷贝,且copy是一个纯对象或数组
|
|
|
|
|
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
|
|
|
|
|
( copyIsArray = jQuery.isArray( copy ) ) ) ) {
|
|
|
|
|
|
|
|
|
|
// 如果copy是数组
|
|
|
|
|
if ( copyIsArray ) {
|
|
|
|
|
copyIsArray = false; // 重置标志位,因为已经处理过是数组的情况
|
|
|
|
|
clone = src && jQuery.isArray( src ) ? src : []; // 根据src是否为数组来决定clone是src的引用还是新数组
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// 如果copy是纯对象
|
|
|
|
|
clone = src && jQuery.isPlainObject( src ) ? src : {}; // 根据src是否为纯对象来决定clone是src的引用还是新对象
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 从不直接移动原始对象,而是克隆它们
|
|
|
|
|
// 使用jQuery.extend进行合并,可能涉及深度拷贝
|
|
|
|
|
target[ name ] = jQuery.extend( deep, clone, copy );
|
|
|
|
|
|
|
|
|
|
// 如果copy不是undefined,则直接赋值给target[name]
|
|
|
|
|
} else if ( copy !== undefined ) {
|
|
|
|
|
target[ name ] = copy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// jQuery的静态方法集合
|
|
|
|
|
jQuery.extend( {
|
|
|
|
|
|
|
|
|
|
// 每个jQuery实例在页面上的唯一标识符
|
|
|
|
|
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
|
|
|
|
|
|
|
|
|
|
// 假设没有ready模块时,jQuery已准备好
|
|
|
|
|
isReady: true,
|
|
|
|
|
|
|
|
|
|
// 错误处理函数
|
|
|
|
|
error: function( msg ) {
|
|
|
|
|
throw new Error( msg );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 空函数,常用于回调占位
|
|
|
|
|
noop: function() {},
|
|
|
|
|
|
|
|
|
|
// 判断对象是否为函数
|
|
|
|
|
isFunction: function( obj ) {
|
|
|
|
|
return jQuery.type( obj ) === "function";
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断对象是否为数组
|
|
|
|
|
isArray: Array.isArray || function( obj ) {
|
|
|
|
|
return jQuery.type( obj ) === "array";
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断对象是否为窗口对象
|
|
|
|
|
isWindow: function( obj ) {
|
|
|
|
|
return obj != null && obj == obj.window;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断对象是否为数字
|
|
|
|
|
isNumeric: function( obj ) {
|
|
|
|
|
var realStringObj = obj && obj.toString();
|
|
|
|
|
return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断对象是否为空对象
|
|
|
|
|
isEmptyObject: function( obj ) {
|
|
|
|
|
var name;
|
|
|
|
|
for ( name in obj ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断对象是否为纯对象(即直接通过{}或new Object()创建的对象)
|
|
|
|
|
isPlainObject: function( obj ) {
|
|
|
|
|
var key;
|
|
|
|
|
if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if ( obj.constructor &&
|
|
|
|
|
!hasOwn.call( obj, "constructor" ) &&
|
|
|
|
|
!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} catch ( e ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( !support.ownFirst ) {
|
|
|
|
|
for ( key in obj ) {
|
|
|
|
|
return hasOwn.call( obj, key );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for ( key in obj ) {}
|
|
|
|
|
return key === undefined || hasOwn.call( obj, key );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取对象的类型
|
|
|
|
|
type: function( obj ) {
|
|
|
|
|
if ( obj == null ) {
|
|
|
|
|
return obj + "";
|
|
|
|
|
}
|
|
|
|
|
return typeof obj === "object" || typeof obj === "function" ?
|
|
|
|
|
class2type[ toString.call( obj ) ] || "object" :
|
|
|
|
|
typeof obj;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 在全局上下文中执行JavaScript代码
|
|
|
|
|
globalEval: function( data ) {
|
|
|
|
|
if ( data && jQuery.trim( data ) ) {
|
|
|
|
|
( window.execScript || function( data ) {
|
|
|
|
|
window[ "eval" ].call( window, data );
|
|
|
|
|
} )( data );
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 将字符串从dashed转换为camelCase
|
|
|
|
|
camelCase: function( string ) {
|
|
|
|
|
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 检查元素是否具有指定的节点名称
|
|
|
|
|
nodeName: function( elem, name ) {
|
|
|
|
|
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 遍历对象或数组
|
|
|
|
|
each: function( obj, callback ) {
|
|
|
|
|
var length, i = 0;
|
|
|
|
|
if ( isArrayLike( obj ) ) {
|
|
|
|
|
length = obj.length;
|
|
|
|
|
for ( ; i < length; i++ ) {
|
|
|
|
|
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for ( i in obj ) {
|
|
|
|
|
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return obj;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
// Support: Android<4.1, IE<9
|
|
|
|
|
// 去除字符串两端的空白字符
|
|
|
|
|
trim: function(text) {
|
|
|
|
|
// 如果text为null或undefined,则返回空字符串
|
|
|
|
|
return text == null ?
|
|
|
|
|
"" :
|
|
|
|
|
// 将text转换为字符串(如果text不是字符串的话),然后使用正则表达式去除两端的空白字符
|
|
|
|
|
(text + "").replace(rtrim, "");
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 将类数组对象或可迭代对象转换为真正的数组,results是内部使用参数,用于存储转换结果
|
|
|
|
|
makeArray: function(arr, results) {
|
|
|
|
|
var ret = results || []; // 如果没有传入results,则初始化为空数组
|
|
|
|
|
|
|
|
|
|
if (arr != null) { // 如果arr不为null或undefined
|
|
|
|
|
// 判断arr是否类似数组
|
|
|
|
|
if (isArrayLike(Object(arr))) {
|
|
|
|
|
// 如果是字符串,则将其放入数组中,否则直接使用arr
|
|
|
|
|
jQuery.merge(ret,
|
|
|
|
|
typeof arr === "string" ?
|
|
|
|
|
[arr] : arr
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 使用push方法将arr添加到ret数组中
|
|
|
|
|
push.call(ret, arr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret; // 返回转换后的数组
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 判断元素是否在数组中,返回元素的索引,如果不在则返回-1
|
|
|
|
|
inArray: function(elem, arr, i) {
|
|
|
|
|
var len;
|
|
|
|
|
|
|
|
|
|
if (arr) { // 如果数组不为空
|
|
|
|
|
if (indexOf) { // 如果indexOf方法存在(现代浏览器)
|
|
|
|
|
return indexOf.call(arr, elem, i); // 使用indexOf查找元素索引
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
len = arr.length; // 获取数组长度
|
|
|
|
|
i = i ? i < 0 ? Math.max(0, len + i) : i : 0; // 处理负数索引
|
|
|
|
|
|
|
|
|
|
// 遍历数组,查找元素
|
|
|
|
|
for (; i < len; i++) {
|
|
|
|
|
// 跳过稀疏数组中的空位
|
|
|
|
|
if (i in arr && arr[i] === elem) {
|
|
|
|
|
return i; // 找到元素,返回索引
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1; // 未找到元素,返回-1
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 合并两个数组,将第二个数组的元素添加到第一个数组中
|
|
|
|
|
merge: function(first, second) {
|
|
|
|
|
var len = +second.length, // 获取second数组的长度(转换为数字)
|
|
|
|
|
j = 0,
|
|
|
|
|
i = first.length; // 获取first数组的长度
|
|
|
|
|
|
|
|
|
|
// 使用while循环将second数组的元素添加到first数组中
|
|
|
|
|
while (j < len) {
|
|
|
|
|
first[i++] = second[j++];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 支持IE<9,处理类数组对象(如NodeLists)的长度属性可能不是数字的情况
|
|
|
|
|
if (len !== len) { // 如果len转换为数字后与自身不相等(NaN的情况)
|
|
|
|
|
while (second[j] !== undefined) { // 继续添加元素,直到second[j]为undefined
|
|
|
|
|
first[i++] = second[j++];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
first.length = i; // 更新first数组的长度
|
|
|
|
|
|
|
|
|
|
return first; // 返回合并后的数组
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 使用回调函数过滤数组,返回满足条件的元素组成的新数组
|
|
|
|
|
grep: function(elems, callback, invert) {
|
|
|
|
|
var callbackInverse,
|
|
|
|
|
matches = [],
|
|
|
|
|
i = 0,
|
|
|
|
|
length = elems.length,
|
|
|
|
|
callbackExpect = !invert; // 根据invert的值确定callback的期望返回值(true或false)
|
|
|
|
|
|
|
|
|
|
// 遍历数组,将满足条件的元素添加到matches数组中
|
|
|
|
|
for (; i < length; i++) {
|
|
|
|
|
callbackInverse = !callback(elems[i], i); // 调用回调函数,并取反
|
|
|
|
|
if (callbackInverse !== callbackExpect) { // 如果回调函数的返回值与期望不符
|
|
|
|
|
matches.push(elems[i]); // 将元素添加到matches数组中
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return matches; // 返回过滤后的数组
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 对数组或对象进行映射,返回一个新数组,数组中的每个元素都是回调函数处理后的结果
|
|
|
|
|
map: function(elems, callback, arg) {
|
|
|
|
|
var length, value,
|
|
|
|
|
i = 0,
|
|
|
|
|
ret = []; // 初始化返回数组
|
|
|
|
|
|
|
|
|
|
// 判断elems是否是类数组对象
|
|
|
|
|
if (isArrayLike(elems)) {
|
|
|
|
|
length = elems.length; // 获取elems的长度
|
|
|
|
|
// 遍历elems数组
|
|
|
|
|
for (; i < length; i++) {
|
|
|
|
|
value = callback(elems[i], i, arg); // 调用回调函数处理元素
|
|
|
|
|
if (value != null) { // 如果回调函数返回值不为null或undefined
|
|
|
|
|
ret.push(value); // 将返回值添加到ret数组中
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果elems不是类数组对象,则遍历其所有属性
|
|
|
|
|
for (i in elems) {
|
|
|
|
|
value = callback(elems[i], i, arg); // 调用回调函数处理元素
|
|
|
|
|
if (value != null) { // 如果回调函数返回值不为null或undefined
|
|
|
|
|
ret.push(value); // 将返回值添加到ret数组中
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用concat方法将ret数组中的嵌套数组展平
|
|
|
|
|
return concat.apply([], ret);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 全局GUID计数器,用于生成唯一标识符
|
|
|
|
|
guid: 1,
|
|
|
|
|
|
|
|
|
|
// 绑定函数到指定上下文,并可选地预设一些参数
|
|
|
|
|
proxy: function(fn, context) {
|
|
|
|
|
var args, proxy, tmp;
|
|
|
|
|
|
|
|
|
|
// 如果context是字符串,则假设是要绑定到fn的某个方法上
|
|
|
|
|
if (typeof context === "string") {
|
|
|
|
|
tmp = fn[context];
|
|
|
|
|
context = fn;
|
|
|
|
|
fn = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查fn是否为函数,如果不是则返回undefined
|
|
|
|
|
if (!jQuery.isFunction(fn)) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存额外参数
|
|
|
|
|
args = slice.call(arguments, 2);
|
|
|
|
|
// 创建代理函数
|
|
|
|
|
proxy = function() {
|
|
|
|
|
// 将预设参数和当前调用的参数合并,然后调用原函数
|
|
|
|
|
return fn.apply(context || this, args.concat(slice.call(arguments)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 设置代理函数的guid,以便可以移除事件监听器等
|
|
|
|
|
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
|
|
|
|
|
|
|
|
|
|
return proxy; // 返回代理函数
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取当前时间的毫秒数
|
|
|
|
|
now: function() {
|
|
|
|
|
return +(new Date()); // 返回Date对象的时间戳(毫秒数)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// jQuery.support已不在核心中使用,但其他项目可能会附加属性到它上面,因此需要保留
|
|
|
|
|
support: support
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 以下代码块由于使用了ES6的Symbol,可能会导致JSHint报错,因此使用注释忽略JSHint的检查
|
|
|
|
|
/* jshint ignore: start */
|
|
|
|
|
if (typeof Symbol === "function") {
|
|
|
|
|
jQuery.fn[Symbol.iterator] = deletedIds[Symbol.iterator]; // 为jQuery对象设置Symbol.iterator属性,以便支持迭代
|
|
|
|
|
}
|
|
|
|
|
/* jshint ignore: end */
|
|
|
|
|
|
|
|
|
|
// 填充class2type映射表
|
|
|
|
|
jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),
|
|
|
|
|
function(i, name) {
|
|
|
|
|
class2type["[object " + name + "]"] = name.toLowerCase(); // 将类名字符串映射为小写形式并存储在class2type对象中
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 判断一个对象是否类似数组
|
|
|
|
|
function isArrayLike(obj) {
|
|
|
|
|
var length = !!obj && "length" in obj && obj.length, // 获取对象的length属性(如果存在的话)
|
|
|
|
|
type = jQuery.type(obj); // 获取对象的类型
|
|
|
|
|
|
|
|
|
|
// 如果对象是函数或window对象,则不是类似数组
|
|
|
|
|
if (type === "function" || jQuery.isWindow(obj)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回对象是否为数组或长度大于0且length-1索引存在的对象(处理稀疏数组)
|
|
|
|
|
return type === "array" || length === 0 ||
|
|
|
|
|
typeof length === "number" && length > 0 && (length - 1) in obj;
|
|
|
|
|
}
|
|
|
|
|
var Sizzle =
|
|
|
|
|
/*!
|
|
|
|
|
* Sizzle CSS Selector Engine v2.2.1
|
|
|
|
|
* http://sizzlejs.com/
|
|
|
|
|
*
|
|
|
|
|
* Copyright jQuery Foundation and other contributors
|
|
|
|
|
* Released under the MIT license
|
|
|
|
|
* http://jquery.org/license
|
|
|
|
|
*
|
|
|
|
|
* Date: 2015-10-17
|
|
|
|
|
*/
|
|
|
|
|
(function(window) {
|
|
|
|
|
// 省略Sizzle选择器引擎的代码...
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个独特的expando属性名,用于存储数据,结合当前时间戳
|
|
|
|
|
expando = "sizzle" + 1 * new Date(),
|
|
|
|
|
|
|
|
|
|
// 获取全局的document对象
|
|
|
|
|
preferredDoc = window.document,
|
|
|
|
|
|
|
|
|
|
// 运行方向计数器(可能用于跟踪查询的执行顺序)
|
|
|
|
|
dirruns = 0,
|
|
|
|
|
|
|
|
|
|
// 完成标志(可能用于跟踪查询是否已完成)
|
|
|
|
|
done = 0,
|
|
|
|
|
|
|
|
|
|
// 创建缓存对象,用于存储类名、标记和编译器的缓存
|
|
|
|
|
classCache = createCache(),
|
|
|
|
|
tokenCache = createCache(),
|
|
|
|
|
compilerCache = createCache(),
|
|
|
|
|
|
|
|
|
|
// 排序函数,用于排序,如果两个元素相同,则标记有重复
|
|
|
|
|
sortOrder = function( a, b ) {
|
|
|
|
|
if ( a === b ) {
|
|
|
|
|
hasDuplicate = true;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 常量定义
|
|
|
|
|
MAX_NEGATIVE = 1 << 31,
|
|
|
|
|
|
|
|
|
|
// 实例方法
|
|
|
|
|
hasOwn = ({}).hasOwnProperty,
|
|
|
|
|
arr = [],
|
|
|
|
|
pop = arr.pop,
|
|
|
|
|
push_native = arr.push,
|
|
|
|
|
push = arr.push, // 注意:这里push被重新赋值,下面会解释
|
|
|
|
|
slice = arr.slice,
|
|
|
|
|
|
|
|
|
|
// 自定义的indexOf方法,用于查找元素在数组中的位置
|
|
|
|
|
indexOf = function( list, elem ) {
|
|
|
|
|
var i = 0,
|
|
|
|
|
len = list.length;
|
|
|
|
|
for ( ; i < len; i++ ) {
|
|
|
|
|
if ( list[i] === elem ) {
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 布尔属性字符串,用于快速检查元素是否具有这些属性
|
|
|
|
|
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
|
|
|
|
|
|
|
|
|
|
// 正则表达式定义,用于匹配不同类型的选择器
|
|
|
|
|
// ...(省略了具体的正则表达式定义,因为它们很长且专注于选择器解析)
|
|
|
|
|
|
|
|
|
|
// matchExpr对象,用于快速匹配不同类型的选择器
|
|
|
|
|
matchExpr = {
|
|
|
|
|
// ...(省略了具体的匹配规则)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 用于检查元素类型的正则表达式
|
|
|
|
|
rinputs = /^(?:input|select|textarea|button)$/i,
|
|
|
|
|
rheader = /^h\d$/i,
|
|
|
|
|
|
|
|
|
|
// 用于检测原生方法的正则表达式
|
|
|
|
|
rnative = /^[^{]+\{\s*\[native \w/,
|
|
|
|
|
|
|
|
|
|
// 快速表达式匹配,用于解析ID、标签或类选择器
|
|
|
|
|
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
|
|
|
|
|
|
|
|
|
|
// 用于匹配兄弟选择器的正则表达式
|
|
|
|
|
rsibling = /[+~]/,
|
|
|
|
|
rescape = /'|\\/g,
|
|
|
|
|
|
|
|
|
|
// CSS转义字符的正则表达式和函数
|
|
|
|
|
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
|
|
|
|
|
funescape = function( _, escaped, escapedWhitespace ) {
|
|
|
|
|
// ...(省略了具体的转义逻辑)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 卸载处理器,用于在页面卸载时重置文档
|
|
|
|
|
unloadHandler = function() {
|
|
|
|
|
setDocument();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 优化push.apply的使用,尝试直接应用NodeList到数组
|
|
|
|
|
try {
|
|
|
|
|
push.apply(
|
|
|
|
|
(arr = slice.call( preferredDoc.childNodes )),
|
|
|
|
|
preferredDoc.childNodes
|
|
|
|
|
);
|
|
|
|
|
// 尝试访问最后一个元素的nodeType来检测是否成功
|
|
|
|
|
arr[ preferredDoc.childNodes.length ].nodeType;
|
|
|
|
|
} catch ( e ) {
|
|
|
|
|
// 如果失败,则使用替代的push方法
|
|
|
|
|
push = { apply: arr.length ?
|
|
|
|
|
|
|
|
|
|
// 如果原生slice可用,则使用它
|
|
|
|
|
function( target, els ) {
|
|
|
|
|
push_native.apply( target, slice.call(els) );
|
|
|
|
|
} :
|
|
|
|
|
|
|
|
|
|
// 否则,逐个添加元素到目标数组
|
|
|
|
|
function( target, els ) {
|
|
|
|
|
var j = target.length,
|
|
|
|
|
i = 0;
|
|
|
|
|
while ( (target[j++] = els[i++]) ) {}
|
|
|
|
|
target.length = j - 1;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sizzle函数,是选择器引擎的核心
|
|
|
|
|
function Sizzle( selector, context, results, seed ) {
|
|
|
|
|
var m, i, elem, nid, nidselect, match, groups, newSelector,
|
|
|
|
|
newContext = context && context.ownerDocument,
|
|
|
|
|
nodeType = context ? context.nodeType : 9; // 9代表document节点
|
|
|
|
|
|
|
|
|
|
results = results || [];
|
|
|
|
|
|
|
|
|
|
// 如果选择器不是字符串、为空、或者上下文节点类型不正确,则直接返回空结果
|
|
|
|
|
if ( typeof selector !== "string" || !selector ||
|
|
|
|
|
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
// ...(省略了Sizzle函数的具体实现,因为这部分非常长且复杂)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果seed参数为假(即没有提供初始的匹配元素集合)
|
|
|
|
|
if ( !seed ) {
|
|
|
|
|
|
|
|
|
|
// 如果提供的上下文(context)不是当前文档,则设置文档为上下文
|
|
|
|
|
// 如果没有提供上下文,则使用preferredDoc(优先文档)作为上下文
|
|
|
|
|
// 如果上下文是一个元素,则使用它的ownerDocument(拥有它的文档)作为上下文
|
|
|
|
|
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
|
|
|
|
|
setDocument( context );
|
|
|
|
|
}
|
|
|
|
|
// 如果没有提供上下文,则默认使用document作为上下文
|
|
|
|
|
context = context || document;
|
|
|
|
|
|
|
|
|
|
// 如果文档是HTML文档
|
|
|
|
|
if ( documentIsHTML ) {
|
|
|
|
|
|
|
|
|
|
// 如果选择器足够简单,尝试使用"get*By*" DOM方法(除了DocumentFragment上下文,因为DocumentFragment上没有这些方法)
|
|
|
|
|
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
|
|
|
|
|
|
|
|
|
|
// ID选择器
|
|
|
|
|
if ( (m = match[1]) ) {
|
|
|
|
|
|
|
|
|
|
// 文档上下文
|
|
|
|
|
if ( nodeType === 9 ) {
|
|
|
|
|
if ( (elem = context.getElementById( m )) ) {
|
|
|
|
|
// 支持:IE, Opera, Webkit
|
|
|
|
|
// TODO: 识别版本
|
|
|
|
|
// getElementById可能会根据名称而不是ID匹配元素
|
|
|
|
|
if ( elem.id === m ) {
|
|
|
|
|
results.push( elem );
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 元素上下文
|
|
|
|
|
} else {
|
|
|
|
|
// 支持:IE, Opera, Webkit
|
|
|
|
|
// TODO: 识别版本
|
|
|
|
|
// getElementById可能会根据名称而不是ID匹配元素
|
|
|
|
|
if ( newContext && (elem = newContext.getElementById( m )) &&
|
|
|
|
|
contains( context, elem ) &&
|
|
|
|
|
elem.id === m ) {
|
|
|
|
|
|
|
|
|
|
results.push( elem );
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 类型选择器
|
|
|
|
|
} else if ( match[2] ) {
|
|
|
|
|
push.apply( results, context.getElementsByTagName( selector ) );
|
|
|
|
|
return results;
|
|
|
|
|
|
|
|
|
|
// 类选择器
|
|
|
|
|
} else if ( (m = match[3]) && support.getElementsByClassName &&
|
|
|
|
|
context.getElementsByClassName ) {
|
|
|
|
|
|
|
|
|
|
push.apply( results, context.getElementsByClassName( m ) );
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用querySelectorAll
|
|
|
|
|
if ( support.qsa &&
|
|
|
|
|
!compilerCache[ selector + " " ] &&
|
|
|
|
|
(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
|
|
|
|
|
|
|
|
|
|
if ( nodeType !== 1 ) {
|
|
|
|
|
newContext = context;
|
|
|
|
|
newSelector = selector;
|
|
|
|
|
|
|
|
|
|
// qSA会在元素上下文之外查找,这不是我们想要的
|
|
|
|
|
// 感谢Andrew Dupont提供的这种解决方法
|
|
|
|
|
// 支持:IE <=8
|
|
|
|
|
// 排除对象元素
|
|
|
|
|
} else if ( context.nodeName.toLowerCase() !== "object" ) {
|
|
|
|
|
|
|
|
|
|
// 捕获上下文的ID,如果必要则先设置它
|
|
|
|
|
if ( (nid = context.getAttribute( "id" )) ) {
|
|
|
|
|
nid = nid.replace( rescape, "\\$&" );
|
|
|
|
|
} else {
|
|
|
|
|
context.setAttribute( "id", (nid = expando) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 在列表中的每个选择器前加上前缀
|
|
|
|
|
groups = tokenize( selector );
|
|
|
|
|
i = groups.length;
|
|
|
|
|
nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
groups[i] = nidselect + " " + toSelector( groups[i] );
|
|
|
|
|
}
|
|
|
|
|
newSelector = groups.join( "," );
|
|
|
|
|
|
|
|
|
|
// 为兄弟选择器扩展上下文
|
|
|
|
|
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
|
|
|
|
context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( newSelector ) {
|
|
|
|
|
try {
|
|
|
|
|
push.apply( results,
|
|
|
|
|
newContext.querySelectorAll( newSelector )
|
|
|
|
|
);
|
|
|
|
|
return results;
|
|
|
|
|
} catch ( qsaError ) {
|
|
|
|
|
} finally {
|
|
|
|
|
if ( nid === expando ) {
|
|
|
|
|
context.removeAttribute( "id" );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 其他情况
|
|
|
|
|
return select( selector.replace( rtrim, "$1" ), context, results, seed );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建一个有限大小的键值缓存
|
|
|
|
|
* @returns {function(string, object)} 返回一个在存储后将数据对象存储在自身上的函数,属性名为(空格后缀)字符串,
|
|
|
|
|
* 如果缓存大于Expr.cacheLength,则删除最旧的条目
|
|
|
|
|
*/
|
|
|
|
|
function createCache() {
|
|
|
|
|
var keys = [];
|
|
|
|
|
|
|
|
|
|
function cache( key, value ) {
|
|
|
|
|
// 使用(key + " ")以避免与原生原型属性冲突(见Issue #157)
|
|
|
|
|
if ( keys.push( key + " " ) > Expr.cacheLength ) {
|
|
|
|
|
// 仅保留最近的条目
|
|
|
|
|
delete cache[ keys.shift() ];
|
|
|
|
|
}
|
|
|
|
|
return (cache[ key + " " ] = value);
|
|
|
|
|
}
|
|
|
|
|
return cache;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为Sizzle特殊使用标记一个函数
|
|
|
|
|
* @param {Function} fn 要标记的函数
|
|
|
|
|
*/
|
|
|
|
|
function markFunction( fn ) {
|
|
|
|
|
fn[ expando ] = true;
|
|
|
|
|
return fn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用一个元素支持测试
|
|
|
|
|
* @param {Function} fn 传入创建的div,并期望一个布尔结果
|
|
|
|
|
*/
|
|
|
|
|
function assert( fn ) {
|
|
|
|
|
var div = document.createElement("div");
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return !!fn( div );
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return false;
|
|
|
|
|
} finally {
|
|
|
|
|
// 默认情况下从其父节点中移除
|
|
|
|
|
if ( div.parentNode ) {
|
|
|
|
|
div.parentNode.removeChild( div );
|
|
|
|
|
}
|
|
|
|
|
// 在IE中释放内存
|
|
|
|
|
div = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为指定的attrs添加相同的处理器
|
|
|
|
|