jQuery源码学习(版本1.11)-扩展工具函数

来源:互联网 发布:防骗数据库 编辑:程序博客网 时间:2024/06/07 17:40

概述

本文详细分析jquery-1.11.1.js源码文件行数:174~584;

代码简介:定义了extend()函数,并用其扩展了一些常用的工具函数;

下文进行详细代码分析。


extend():jQuery的继承方法

// 同时被jQuery以及jQuery.fn即原型引用,便于后面扩展各自的函数// 扩展进jQuery的函数,可通过jQuery这个命名空间去调用,称之为工具函数,是jQuery较底层的实现,内部很多其他函数都调用了工具函数// 扩展进jQuery.fn的函数,是所有JQ对象的公用函数jQuery.extend = jQuery.fn.extend = function() {var src, copyIsArray, copy, name, options, clone,// 初始化要扩展的target是第一个参数,如果arguments[0]是false,即表示浅拷贝,则target直接设置成新对象target = arguments[0] || {},i = 1,length = arguments.length,// 默认是浅拷贝deep = false;// 从前面初始化可以看出,如果成立则肯定为深度拷贝,则传给deepif ( typeof target === "boolean" ) {deep = target;// 再将target指向第二个参数target = arguments[ i ] || {};i++;}// 兼容target不是对象或函数的场景if ( typeof target !== "object" && !jQuery.isFunction(target) ) {target = {};}// 由前面的代码可以看出,i永远领先1的,如果i === length成立,则表示设置了target = arguments[i--]||{}之后,就已经没有其他参数可以用于扩展了// 则target = this;把需要扩展的对象设置成调用者本身,而顺理成章arguments[i--]成为被扩展的对象if ( i === length ) {target = this;i--;}// 以上的代码都是对arguments的不同情况进行兼容,确定需要扩展的对象target,以及deep,还有i的值// i的值用于后面遍历arguments,arguments[i]以上的参数就是被扩展的对象了for ( ; i < length; i++ ) {// options指向arguments[ i ]并进行空判断if ( (options = arguments[ i ]) != null ) {// 循环遍历options每一个可扩展属性for ( name in options ) {src = target[ name ];copy = options[ name ];// 防止循环引用自身if ( target === copy ) {continue;}// 处理深度拷贝场景,需要判断copy是否能拷贝,jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) 满足即可if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {if ( copyIsArray ) {// copyIsArray = false;这行我个人觉得是多余的,后面没有使用copyIsArray的地方了copyIsArray = false;// 如果src不为空且是类数组结构,则直接将src作为扩展对象进入下一轮递归,原内容会被替换掉clone = src && jQuery.isArray(src) ? src : [];} else {// 同上如果src不为空且是纯粹的对象({}或new Object()建立的),则直接将src作为扩展对象进入下一轮递归,原内容会被替换掉clone = src && jQuery.isPlainObject(src) ? src : {};}// 进入递归target[ name ] = jQuery.extend( deep, clone, copy );// 被扩展属性不为空则扩展进target[ name ]} else if ( copy !== undefined ) {target[ name ] = copy;}}}}// 返回扩展之后的targetreturn target;};


扩展一些常用工具函数

jQuery.extend({// Unique for each copy of jQuery on the pageexpando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),// Assume jQuery is ready without the ready moduleisReady: 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 ) {/* 使用&&先判断obj是否为空,防止出错return obj != null && obj == obj.window;},isNumeric: function( obj ) {// 判断obj是否为数字,parseFloat(obj)将null|true|false|""全部转成NaN,这样就必定返回false// 如果直接判断obj >= 0,假设obj=true  true > 0是返回true的return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0;},// 使用for in 判断obj是否为emptyisEmptyObject: function( obj ) {var name;for ( name in obj ) {return false;}return true;},// 判断是否为纯粹对象isPlainObject: function( obj ) {var key;// 参数校验,必须是object且不能为dom对象,window对象if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {return false;}try {// 一般情况下,constructor属性都是在prototype对象里面的(自身属性一般不定义constructor)// 因此hasOwn.call(obj, "constructor")都一般为false,即可用obj.constructor.prototype判断isPrototypeOf,否则不适用判断isPrototypeOf// isPrototypeOf属性一般存在Object的原型里,通过obj.constructor.prototype找到obj原型,然后hasOwn判断则确定是否为纯粹对象// 原型重新定义过,或者通过继承得到的新对象的实例,hasOwn.call(obj.constructor.prototype, "isPrototypeOf")都会返回falseif ( obj.constructor &&!hasOwn.call(obj, "constructor") &&!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {return false;}} catch ( e ) {return false;}// IE < 9下,自身属性枚举会靠后,判断第一个即可if ( support.ownLast ) {for ( key in obj ) {return hasOwn.call( obj, key );}}// Own properties枚举时都会优先,因此判断最后一个可枚举的属性即可for ( key in obj ) {}return key === undefined || hasOwn.call( obj, key );},type: function( obj ) {// undefined或null则将其转换成字符串返回if ( obj == null ) {return obj + "";}// 从class2type取出对应值返回,相比直接返回typeof值,利用class2type可以识别更多的BOM对象return typeof obj === "object" || typeof obj === "function" ?class2type[ toString.call(obj) ] || "object" :typeof obj;},// Evaluates a script in a global context// Workarounds based on findings by Jim Driscoll// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-contextglobalEval: function( data ) {if ( data && jQuery.trim( data ) ) {// We use execScript on Internet Explorer// We use an anonymous function so that context is window// rather than jQuery in Firefox( window.execScript || function( data ) {window[ "eval" ].call( window, data );} )( data );}},// 改为驼峰写法,用于后面css和data modules// rmsPrefix兼容IEcamelCase: function( string ) {return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );},// 判断elem.nodeName.toLowerCase() === name.toLowerCase()nodeName: function( elem, name ) {return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();},// each会对传入的obj执行callback回调函数,args则是callback的参数each: function( obj, callback, args ) {var value,i = 0,length = obj.length,isArray = isArraylike( obj );// 分场景处理,args存在if ( args ) {// isArray为true则用遍历数组方法遍历obj,apply执行callbackif ( isArray ) {for ( ; i < length; i++ ) {value = callback.apply( obj[ i ], args );// 可用于及早停止循环if ( value === false ) {break;}}} else {// isArray为false则用for in 遍历obj,执行callbackfor ( i in obj ) {value = callback.apply( obj[ i ], args );if ( value === false ) {break;}}}// args不存在,则把i,以及自身obj[ i ]作参数,调用call传给callback执行} else {if ( isArray ) {for ( ; i < length; i++ ) {value = callback.call( obj[ i ], i, obj[ i ] );if ( value === false ) {break;}}} else {for ( i in obj ) {value = callback.call( obj[ i ], i, obj[ i ] );if ( value === false ) {break;}}}}return obj;},// rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,其中\uFEFF是字节序标记,\xA0是拉丁文中的空白trim: function( text ) {return text == null ?"" :( text + "" ).replace( rtrim, "" );},// makeArray是内部使用的方法// 因为是内部方法,从代码分析可知没有校验results的合法性makeArray: function( arr, results ) {var ret = results || [];if ( arr != null ) {// Object(arr)会将arra转成类数组对象,转成功则调用merge合并ret跟arrif ( isArraylike( Object(arr) ) ) {jQuery.merge( ret,typeof arr === "string" ?[ arr ] : arr);// 否则直接push进ret} else {push.call( ret, arr );}}// 返回retreturn ret;},// 判断elem是否在arr里,i表示从第arr[i]开始往后判断inArray: function( elem, arr, i ) {var len;if ( arr ) {// 存在indexOf则直接调用js Array 的indexof查找if ( indexOf ) {return indexOf.call( arr, elem, i );}// 不存在indexOf则重新实现一个len = arr.length;// 校验i的值,根据情况处理,正值直接使用,负值使用Math.max( 0, len + i )i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;for ( ; i < len; i++ ) {// 判断是否查找成功if ( i in arr && arr[ i ] === elem ) {return i;}}}// 查找失败返回-1return -1;},// 合并数组或者类数组对象如{0:"a", 1:"b", length:2}(对象必须有length属性)merge: function( first, second ) {var len = +second.length,j = 0,i = first.length;while ( j < len ) {first[ i++ ] = second[ j++ ];}// 兼容在IE < 9下,存在类数组对象的length为NaN的,如NodeList(NaN !== NaN返回true,NaN == NaN是false)// 从这里可以看出如果first.length为NaN,则会出错,因此必须保证first是形式正确的类数组对象if ( len !== len ) {while ( second[j] !== undefined ) {first[ i++ ] = second[ j++ ];}}// 最终更新length并且返回firstfirst.length = i;return first;},// elems中寻找满足callback的值,并返回数组集合,invert用于校验callback返回值,不传默认是truegrep: function( elems, callback, invert ) {var callbackInverse,matches = [],i = 0,length = elems.length,// invert不传则为undefined,!invert则为true,即默认为true// 但如果传入值,则找相反的项callbackExpect = !invert;for ( ; i < length; i++ ) {// 遍历elems,将elems[ i ]和i传入callback,获取其返回值// 这里加上!是为了作数据转换,保证得到的是boolean值callbackInverse = !callback( elems[ i ], i );if ( callbackInverse !== callbackExpect ) {//满足条件塞进matchesmatches.push( elems[ i ] );}}return matches;},// elems里每一项均执行callback,返回其执行结果的集合// 参数arg仅供内部调用时使用map: function( elems, callback, arg ) {var value,i = 0,length = elems.length,isArray = isArraylike( elems ),ret = [];// 是类数组则遍历数组if ( isArray ) {for ( ; i < length; i++ ) {value = callback( elems[ i ], i, arg );if ( value != null ) {ret.push( value );}}// 非数组则用for in遍历} else {for ( i in elems ) {value = callback( elems[ i ], i, arg );if ( value != null ) {ret.push( value );}}}// 数组ret里的值有可能还是数组,使用concat可以进一步躺平// concat可通过传入多个参数连接多个数组,因此如果ret里的值全是数组,则传入ret相当于argumentsreturn concat.apply( [], ret );},// A global GUID counter for objectsguid: 1,// 绑定方法fn与其执行上下文context,返回一个可执行的方法proxy()proxy: function( fn, context ) {var args, proxy, tmp;// 如果传入context是字符串,则将其改为fn,而原fn改为其属性fn[ context ]if ( typeof context === "string" ) {tmp = fn[ context ];context = fn;fn = tmp;}// 校验入参fn,不正确返回undefinedif ( !jQuery.isFunction( fn ) ) {return undefined;}// 截取arguments后面的值,作为fn调用时的参数// 注意这里的arguments是proxy: function( fn, context ) 的argumentsargs = slice.call( arguments, 2 );// 创建proxy,实现是执行fn.apply达到绑定context的效果proxy = function() {// 注意这里的arguments是proxy = function()的,与上面的不一样// 使用concat会返回新的数组,就不会影响proxy: function( fn, context )的参数return fn.apply( context || this, args.concat( slice.call( arguments ) ) );};// 设置唯一的guid,方便日后删除对应的handlerproxy.guid = fn.guid = fn.guid || jQuery.guid++;return proxy;},// 返回日期now: function() {return +( new Date() );},// 将之前定义的support对象传入jQuery命名空间中,作为工具使用support: support});// 建立class2type对象保存类型map:{"[object Object]": "object", "[object Number]": "number",...}// 前面代码定义纯粹对象var class2type = {}; 以及var toString = class2type.toString;// 调用toString()返回"[object Object]",而对toString使用call即toString.call(),如果传入Number类型则返回"[object Number]",如此类推,返回的全是BOM对象的字符串// JQ利用这点来作类型判断,更详细知识原理暂时不懂jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {class2type[ "[object " + name + "]" ] = name.toLowerCase();});// isArraylike是内部用于判断obj是否为类数组对象,string类型会报错(因为是内部使用,因此没必要严格校验,只需要根据内部使用的情况校验入参)function isArraylike( obj ) {var length = obj.length,type = jQuery.type( obj );if ( type === "function" || jQuery.isWindow( obj ) ) {return false;}// nodeType === 1即是ELEMENT_NODE,但如果存在length,也返回trueif ( obj.nodeType === 1 && length ) {return true;}// &&优先级高于||,因此可以分为三段,同时满足则返回true,我个人觉得length === 0是多余的,于之后length > 0冲突return type === "array" || length === 0 ||typeof length === "number" && length > 0 && ( length - 1 ) in obj;}



0 0