- // 扩展工具函数
- jQuery.extend({
- // 释放 $ 的 jQuery 控制权
- // 许多 JavaScript 库使用 $ 作为函数或变量名, jQuery 也一样.
- // 在 jQuery 中,$ 仅仅是 jQuery 的别名, 因此即使不使用 $ 也能保证所有功能性.
- // 假如我们需要使用 jQuery 之外的另一 JavaScript 库, 我们可以通过调用 $.noConflict() 向该库返回控制权.
- // 通过向该方法传递参数 true, 我们可以将 $ 和 jQuery 的控制权都交还给另一 JavaScript 库.
- noConflict: function( deep ) {
- // 交出 $ 的控制权
- if ( Windows.$ === jQuery ) {
- Windows.$ = _$;
- }
- // 交出 jQuery 的控制权
- if ( deep && Windows.jQuery === jQuery ) {
- Windows.jQuery = _jQuery;
- }
- return jQuery;
- },
- // Is the DOM ready to be used? Set to true once it occurs.
- isReady: false,
- // A counter to track how many items to wait for before
- // the ready event fires. See #6781
- // 一个计数器, 用于跟踪在 ready 事件出发前的等待次数
- readyWait: 1,
- // Hold (or release) the ready event
- // 继续等待或触发
- holdReady: function( hold ) {
- if ( hold ) {
- jQuery.readyWait++;
- } else {
- jQuery.ready( true );
- }
- },
- // Handle when the DOM is ready
- // 文档加载完毕句柄
- ready: function( wait ) {
- // Either a released hold or an DOMready/load event and not yet ready
- //
- if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- // 确保 document.body 存在
- if ( !document.body ) {
- return setTimeout( jQuery.ready, 1 );
- }
- // Remember that the DOM is ready
- jQuery.isReady = true;
- // If a normal DOM Ready event fired, decrement, and wait if need be
- if ( wait !== true && --jQuery.readyWait> 0 ) {
- return;
- }
- // If there are functions bound, to execute
- readyList.resolveWith( document, [ jQuery ] );
- // Trigger any bound ready events
- if ( jQuery.fn.trigger ) {
- jQuery( document ).trigger( "ready" ).unbind( "ready" );
- }
- }
- },
- // 初始化 readyList 事件处理函数队列
- // 兼容不同浏览对绑定事件的区别
- bindReady: function() {
- if ( readyList ) {
- return;
- }
- readyList = jQuery._Deferred();
- // Catch cases where $(document).ready() is called after the
- // browser event has already occurred.
- if ( document.readyState === "complete" ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- return setTimeout( jQuery.ready, 1 );
- }
- // Mozilla, Opera and webkit nightlies currently support this event
- // 兼容事件, 通过检测浏览器的功能特性, 而非嗅探浏览器
- if ( document.addEventListener ) {
- // Use the handy event callback
- // 使用较快的加载完毕事件
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
- // A fallback to Windows.onload, that will always work
- // 注册 Windows.onload 回调函数
- Windows.addEventListener( "load", jQuery.ready, false );
- // If IE event model is used
- } else if ( document.attachEvent ) {
- // ensure firing before onload,
- // maybe late but safe also for iframes
- // 确保在 onload 之前触发 onreadystatechange, 可能慢一些但是对 iframes 更安全
- document.attachEvent( "onreadystatechange", DOMContentLoaded );
- // A fallback to Windows.onload, that will always work
- // 注册 Windows.onload 回调函数
- Windows.attachEvent( "onload", jQuery.ready );
- // If IE and not a frame
- // continually check to see if the document is ready
- var toplevel = false;
- try {
- toplevel = Windows.frameElement == null;
- } catch(e) {
- }
- if ( document.documentElement.doScroll && toplevel ) {
- doScrollCheck();
- }
- }
- },
- // See test/unit/core.JS for details concerning isFunction.
- // Since version 1.3, DOM methods and functions like alert
- // aren't supported. They return false on IE (#2968).
- // 是否函数
- isFunction: function( obj ) {
- return jQuery.type(obj) === "function";
- },
- // 是否数组
- // 如果浏览器有内置的 Array.isArray 实现, 就使用浏览器自身的实现方式,
- // 否则将对象转为 String, 看是否为 "[object Array]".
- isArray: Array.isArray || function( obj ) {
- return jQuery.type(obj) === "array";
- },
- // A crude way of determining if an object is a Windows
- // 简单的判断 (判断 setInterval 属性) 是否 Windows 对象
- isWindow: function( obj ) {
- return obj && typeof obj === "object" && "setInterval" in obj;
- },
- // 是否是保留字 NaN
- isNaN: function( obj ) {
- // 等于 null 或 不是数字 或调用 Windows.isNaN 判断
- return obj == null || !rdigit.test( obj ) || isNaN( obj );
- },
- // 获取对象的类型
- type: function( obj ) {
- // 通过核心 API 创建一个对象, 不需要 new 关键字
- // 普通函数不行
- // 调用 Object.prototype.toString 方法, 生成 "[object Xxx]" 格式的字符串
- // class2type[ "[object" + name + "]" ] = name.toLowerCase();
- return obj == null ?
- String( obj ) :
- class2type[ toString.call(obj) ] || "object";
- },
- // 检查 obj 是否是一个纯粹的对象(通过 "{}" 或 "new Object" 创建的对象)
- // console.info( $.isPlainObject( {} ) ); // true
- // console.info( $.isPlainObject( '' ) ); // false
- // console.info( $.isPlainObject( document.location ) ); // true
- // console.info( $.isPlainObject( document ) ); // false
- // console.info( $.isPlainObject( new Date() ) ); // false
- // console.info( $.isPlainObject( ) ); // false
- // isPlainObject 分析与重构 https://www.jb51.net/article/25047.htm
- // 对 jQuery.isPlainObject()的理解 https://www.cnblogs.com/phpmix/articles/1733599.html
- isPlainObject: function( obj ) {
- // Must be an Object.
- // Because of IE, we also have to check the presence of the constructor property.
- // Make sure that DOM nodes and Windows objects don't pass through, as well
- // 必须是一个对象
- // 因为在 IE8 中会抛出非法指针异常, 必须检查 constructor 属性
- // DOM 节点和 Windows 对象, 返回 false
- // obj 不存在 或 非 object 类型 或 DOM 节点 或 widnow 对象, 直接返回 false
- // 测试以下三中可能的情况:
- // jQuery.type(obj) !== "object" 类型不是 object, 忽略
- // obj.nodeType 认为 DOM 节点不是纯对象
- // jQuery.isWindow( obj ) 认为 Windows 不是纯对象
- if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
- return false;
- }
- // Not own constructor property must be Object
- // 测试 constructor 属性
- // 具有构造函数 constructor, 却不是自身的属性(即通过 prototype 继承的),
- if ( obj.constructor &&
- !hasOwn.call(obj, "constructor") &&
- !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
- return false;
- }
- // Own properties are enumerated firstly, so to speed up,
- // if last one is own, then all properties are own.
- var key;
- for ( key in obj ) {
- }
- // key === undefined 及不存在任何属性, 认为是简单的纯对象
- // hasOwn.call( obj, key ) 属性 key 不为空, 且属性 key 的对象自身的(即不是通过 prototype 继承的)
- return key === undefined || hasOwn.call( obj, key );
- },
- // 是否空对象
- isEmptyObject: function( obj ) {
- for ( var name in obj ) {
- return false;
- }
- return true;
- },
- // 抛出一个异常
- error: function( msg ) {
- throw msg;
- },
- // 解析 JSON
- // parseJSON 把一个字符串变成 JSON 对象.
- // 我们一般使用的是 eval.parseJSON 封装了这个操作, 但是 eval 被当作了最后手段.
- // 因为最新 JavaScript 标准中加入了 JSON 序列化和反序列化的 API.
- // 如果浏览器支持这个标准, 则这两个 API 是在 JS 引擎中用 Native Code 实现的, 效率肯定比 eval 高很多.
- // 目前来看, Chrome 和 Firefox4 都支持这个 API.
- parseJSON: function( data ) {
- if ( typeof data !== "string" || !data ) {
- return null;
- }
- // Make sure leading/trailing whitespace is removed (IE can't handle it)
- data = jQuery.trim( data );
- // Attempt to parse using the native JSON parser first
- // 原生 JSON API. 反序列化是 JSON.stringify(object)
- if ( Windows.JSON && Windows.JSON.parse ) {
- return Windows.JSON.parse( data );
- }
- // Make sure the incoming data is actual JSON
- // Logic borrowed from https://json.org/json2.js
- // ... 大致地检查一下字符串合法性
- if ( rvalidchars.test( data.replace( rvalidescape, "@" )
- .replace( rvalidtokens, "]" )
- .replace( rvalidbraces, "")) ) {
- return (new Function( "return" + data ))();
- }
- jQuery.error( "Invalid JSON:" + data );
- },
- // Cross-browser xml parsing
- // (xml & tmp used internally)
- // 解析 xml 跨浏览器
- // parseXML 函数也主要是标准 API 和 IE 的封装.
- // 标准 API 是 DOMParser 对象.
- // 而 IE 使用的是 Microsoft.XMLDOM 的 ActiveXObject 对象.
- parseXML: function( data , xml , tmp ) {
- if ( Windows.DOMParser ) { // Standard 标准 xml 解析器
- tmp = new DOMParser();
- xml = tmp.parseFromString( data , "text/xml" );
- } else { // IE IE 的 xml 解析器
- xml = new ActiveXObject( "Microsoft.XMLDOM" );
- xml.async = "false";
- xml.loadXML( data );
- }
- tmp = xml.documentElement;
- if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
- jQuery.error( "Invalid XML:" + data );
- }
- return xml;
- },
- // 无操作函数
- noop: function() {},
- // Evaluates a script in a global context
- // Workarounds based on findings by Jim Driscoll
- // https://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
- // globalEval 函数把一段脚本加载到全局 context(Windows)中.
- // IE 中可以使用 Windows.execScript.
- // 其他浏览器 需要使用 eval.
- // 因为整个 jQuery 代码都是一整个匿名函数, 所以当前 context 是 jQuery, 如果要将上下文设置为 Windows 则需使用 globalEval.
- globalEval: function( data ) {
- // data 非空
- if ( data && rnotwhite.test( data ) ) {
- // We use execScript on Internet Explorer
- // We use an anonymous function so that context is Windows
- // rather than jQuery in Firefox
- ( Windows.execScript || function( data ) {
- Windows[ "eval" ].call( Windows, data );
- } )( data );
- }
- },
- // 判断节点名称是否相同
- nodeName: function( elem, name ) {
- // 忽略大小写
- return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
- },
- // args is for internal usage only
- // 遍历对象或数组
- each: function( object, callback, args ) {
- var name, i = 0,
- length = object.length,
- isObj = length === undefined || jQuery.isFunction( object );
- // 如果有参数 args, 调用 apply, 上下文设置为当前遍历到的对象, 参数使用 args
- if ( args ) {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.apply( object[ name ], args ) === false ) {
- break;
- }
- }
- } else {
- for ( ; i <length; ) {
- if ( callback.apply( object[ i++ ], args ) === false ) {
- break;
- }
- }
- }
- // A special, fast, case for the most common use of each
- // 没有参数 args 则调用, 则调用 call, 上下文设置为当前遍历到的对象, 参数设置为 key/index 和 value
- } else {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
- break;
- }
- }
- } else {
- for ( ; i < length; ) {
- if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
- break;
- }
- }
- }
- }
- return object;
- },
- // Use native String.trim function wherever possible
- // 尽可能的使用本地 String.trim 方法, 否则先过滤开头的空格, 再过滤结尾的空格
- trim: trim ?
- function( text ) {
- return text == null ?
- "" :
- trim.call( text );
- } :
- // Otherwise use our own trimming functionality
- function( text ) {
- return text == null ?
- "" :
- text.toString().replace( trimLeft, "").replace( trimRight,"" );
- },
- // results is for internal usage only
- // 将伪数组转换为数组
- makeArray: function( array, results ) {
- var ret = results || [];
- if ( array != null ) {
- // The Windows, strings (and functions) also have 'length'
- // The extra typeof function check is to prevent crashes
- // in Safari 2 (See: #3039)
- // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
- // 一大堆浏览器兼容性测试, 真实蛋疼
- var type = jQuery.type( array );
- // 测试: 有没有 length 属性, 字符串, 函数, 正则
- // 不是数组, 连伪数组都不是
- if ( array.length == null
- || type === "string"
- || type === "function"
- || type === "regexp"
- || jQuery.isWindow( array ) ) {
- push.call( ret, array );
- } else {
- // $.type( $('p') ) // object
- jQuery.merge( ret, array );
- }
- }
- return ret;
- },
- //
- inArray: function( elem, array ) {
- // 是否有本地化的 Array.prototype.indexOf
- if ( indexOf ) {
- // 直接调用 Array.prototype.indexOf
- return indexOf.call( array, elem );
- }
- // 遍历数组, 查找是否有完全相等的元素, 并返回下标
- // 循环的小技巧: 把 array.length 存放到 length 变量中, 可以减少一次作用域查找
- for ( var i = 0, length = array.length; i < length; i++ ) {
- if ( array[ i ] === elem ) {
- return i;
- }
- }
- // 如果返回 - 1, 则表示不在数组中
- return -1;
- },
- // 将数组 second 合并到数组 first 中
- merge: function( first, second ) {
- var i = first.length, //
- j = 0;
- // 如果 second 的 length 属性是 Number 类型, 则把 second 当数组处理
- if ( typeof second.length === "number" ) {
- for ( var l = second.length; j < l; j++ ) {
- first[ i++ ] = second[ j ];
- }
- } else {
- // 遍历 second, 将非 undefined 的值添加到 first 中
- while ( second[j] !== undefined ) {
- first[ i++ ] = second[ j++ ];
- }
- }
- // 修正 first 的 length 属性, 因为 first 可能不是真正的数组
- first.length = i;
- return first;
- },
- // 过滤数组, 返回新数组; callback 返回 true 时保留; 如果 inv 为 true,callback 返回 false 才会保留
- grep: function( elems, callback, inv ) {
- var ret = [], retVal;
- inv = !!inv;
- // Go through the array, only saving the items
- // that pass the validator function
- // 遍历数组, 只保留通过验证函数 callback 的元素
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- // 这里 callback 的参数列表为: value, index, 与 each 的习惯一致
- retVal = !!callback( elems[ i ], i );
- // 是否反向选择
- if ( inv !== retVal ) {
- ret.push( elems[ i ] );
- }
- }
- return ret;
- },
- // arg is for internal usage only
- // 将数组或对象 elems 的元素 / 属性, 转化成新的数组
- map: function( elems, callback, arg ) {
- var value, key, ret = [],
- i = 0,
- length = elems.length,
- // jQuery objects are treated as arrays
- // 检测 elems 是否是 (伪) 数组
- // 1. 将 jQuery 对象也当成数组处理
- // 2. 检测 length 属性是否存在, length 等于 0, 或第一个和最后一个元素是否存在, 或 jQuery.isArray 返回 true
- isArray = elems instanceof jQuery
- || length !== undefined && typeof length === "number"
- && ( ( length> 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
- // 是数组或对象的差别, 仅仅是遍历的方式不同, 没有其他的区别
- // Go through the array, translating each of the items to their
- // 遍历数组, 对每一个元素调用 callback, 将返回值不为 null 的值, 存入 ret
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- // 执行 callback, 参数依次为 value, index, arg
- value = callback( elems[ i ], i, arg );
- // 如果返回 null, 则忽略(无返回值的 function 会返回 undefined)
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
- // Go through every key on the object,
- // 遍历对象, 对每一个属性调用 callback, 将返回值不为 null 的值, 存入 ret
- } else {
- for ( key in elems ) {
- // 执行 callback, 参数依次为 value, key, arg
- value = callback( elems[ key ], key, arg );
- // 同上
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
- }
- // Flatten any nested arrays
- // 使嵌套数组变平
- // concat:
- // 如果某一项为数组, 那么添加其内容到末尾.
- // 如果该项目不是数组, 就将其作为单个的数组元素添加到数组的末尾.
- return ret.concat.apply( [], ret );
- },
- // A global GUID counter for objects
- guid: 1,
- // Bind a function to a context, optionally partially applying any
- // arguments.
- // 代理方法: 为 fn 指定上下文(即 this)
- // jQuery.proxy( function, context )
- // jQuery.proxy( context, name )
- proxy: function( fn, context ) {
- // 如果 context 是字符串, 设置上下文为 fn,fn 为 fn[ context ]
- // 即设置 fn 的 context 方法的上下文为 fn(默认不是这样吗???TODO)
- if ( typeof context === "string" ) {
- var tmp = fn[ context ];
- context = fn;
- fn = tmp;
- }
- // Quick check to determine if target is callable, in the spec
- // this throws a TypeError, but we will just return undefined.
- // 快速测试 fn 是否是可调用的(即函数), 在文档说明中, 会抛出一个 TypeError,
- // 但是这里仅返回 undefined
- if ( !jQuery.isFunction( fn ) ) {
- return undefined;
- }
- // Simulated bind
- var args = slice.call( arguments, 2 ), // 从参数列表中去掉 fn,context
- proxy = function() {
- // 设置上下文为 context 和参数
- return fn.apply( context, args.concat( slice.call( arguments ) ) );
- };
- // Set the guid of unique handler to the same of original handler, so it can be removed
- // 统一 guid, 使得 proxy 能够被移除
- proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
- return proxy;
- },
- // Mutifunctional method to get and set values to a collection
- // The value/s can be optionally by executed if its a function
- // 多功能函数, 读取或设置集合的属性值; 值为函数时会被执行
- // fn:jQuery.fn.CSS, jQuery.fn.attr, jQuery.fn.prop
- access: function( elems, key, value, exec, fn, pass ) {
- var length = elems.length;
- // Setting many attributes
- // 如果有多个属性, 则迭代
- if ( typeof key === "object" ) {
- for ( var k in key ) {
- jQuery.access( elems, k, key[k], exec, fn, value );
- }
- return elems;
- }
- // Setting one attribute
- // 只设置一个属性
- if ( value !== undefined ) {
- // Optionally, function values get executed if exec is true
- exec = !pass && exec && jQuery.isFunction(value);
- for ( var i = 0; i < length; i++ ) {
- fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
- }
- return elems;
- }
- // Getting an attribute
- // 读取属性
- return length ? fn( elems[0], key ) : undefined;
- },
- // 获取当前时间的便捷函数
- now: function() {
- return (new Date()).getTime();
- },
- // Use of jQuery.browser is frowned upon.
- // More details: https://docs.jquery.com/Utilities/jQuery.browser
- // 不赞成使用 jQuery.browser, 推荐使用 jQuery.support
- // Navigator 正在使用的浏览器的信息
- // Navigator.userAgent 一个只读的字符串, 声明了浏览器用于 HTPP 请求的用户代理头的值
- uaMatch: function( ua ) {
- ua = ua.toLowerCase();
- // 依次匹配各浏览器
- var match = rwebkit.exec( ua ) ||
- ropera.exec( ua ) ||
- rmsie.exec( ua ) ||
- ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
- [];
- // match[1] || ""// match[1]为 false(空字符串, null,undefined,0 等)时, 默认为""
- // match[2] || "0"
- // match[2]为 false(空字符串, null,undefined,0 等)时, 默认为 "0"
- return { browser: match[1] || "", version: match[2] ||"0"
- };
- },
- // 创建一个新的 jQuery 副本, 副本的属性和方法可以被改变, 但是不会影响原始的 jQuery 对象
- // 有两种用法:
- // 1. 覆盖 jQuery 的方法, 而不破坏原始的方法
- // 2. 封装, 避免命名空间冲突, 可以用来开发 jQuery 插件
- // 值得注意的是, jQuery.sub()函数并不提供真正的隔离, 所有的属性, 方法依然指向原始的 jQuery
- // 如果使用这个方法来开发插件, 建议优先考虑 jQuery UI widget 工程
- sub: function() {
- function jQuerySub( selector, context ) {
- return new jQuerySub.fn.init( selector, context );
- }
- jQuery.extend( true, jQuerySub, this ); // 深度拷贝, 将 jQuery 的所有属性和方法拷贝到 jQuerySub
- jQuerySub.superclass = this;
- jQuerySub.fn = jQuerySub.prototype = this(); //
- jQuerySub.fn.constructor = jQuerySub;
- jQuerySub.sub = this.sub;
- jQuerySub.fn.init = function init( selector, context ) {
- 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;
- var rootjQuerySub = jQuerySub(document);
- return jQuerySub;
- },
- // 浏览器类型和版本:
- // $.browser.msie/mozilla/webkit/opera
- // $.browser.version
- // 不推荐嗅探浏览器类型 jQuery.browser, 而是检查浏览器的功能特性 jQuery.support
- // 未来 jQuery.browser 可能会移到一个插件中
- browser: {
- }
- });
来源: https://www.2cto.com/kf/201806/756699.html