extend是jquery常用的一个方法,虽然原生用Object.assign实现了类似的功能。
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {}, //从arguments里拿第一个参数,没传就给默认空对象
i = 1,
length = arguments.length,//参数长度
deep = false;//默认不是深拷贝
// Handle a deep copy situation
//处理深拷贝的情况
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
//判断传参情况,首先target取的是第一个参数,如果target是个布尔值,说明传入了是否深拷贝的布尔值,用argument[1]取到第二个参数给target
// Handle case when target is a string or something (possible in deep copy)
//处理目标值为字符串或者其他值情况
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
//简言之,不是对象就给target一个默认空对象
// Extend jQuery itself if only one argument is passed
//如果就传了一个参数就继承自身
if ( i === length ) {
target = this;
i--;
}
//这里的this指向jQuery
for ( ; i < length; i++ ) {
//第一层循环遍历传入的需要合并的参数对象
//为了方便观察,假设传入 extend(target,obj1,obj2,obj3)传入的第一个参数不是布尔值,所以i值为初始化的1;
// Only deal with non-null/undefined values
//只处理非空值
if ( ( options = arguments[ i ] ) != null ) {
//对传入的在target后的每一个对象进行遍历
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
//target的属性值放进src里,options的属性值放进copy里
// Prevent never-ending loop
//阻止死循环
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
//判断拿到的copy是否为对象或数组并且传入的第一个参数为true则深拷贝,继续深入遍历
if ( copyIsArray ) {
//如果是数组,将copyIsArray重新变为false方便后面遍历的判断,并判断target对象对应的该属性是否为数组,是就赋值给clone,否则赋值空数组
copyIsArray = false;
clone = src && Array.isArray( src ) ? src : [];
} else {
//和上面处理数组一样处理这里面的对象
clone = src && jQuery.isPlainObject( src ) ? src : {};
}
// Never move original objects, clone them
//用前面拿到的clone和copy调用函数自身,实现一层一层进去复制,这里调用自身来做深拷贝很精妙
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
//如果不是深拷贝就好办了,直接把后面的对象对应的属性覆盖的target中对应的属性
target[ name ] = copy;
}
}
}
}
// Return the modified object
//最后把这个修改过的对象传出去
return target;
};
jquery的extend实现很巧妙,在其源码自身多处用到了这个方法,理解这个方法对后续的源码阅读有很大的帮助。
定义完extend后,直接使用extend方法在jquery自身上拓展一些工具类方法和属性,具体方法的实现以及意义暂时忽略不讲,先把源码整体思路理通顺,后续可能会将比较有学习意义的方法拿出来再看看。有意思的一点是在定义extend函数时用到的isFunction、isPlainObject等方法在jquery调用extend函数给自身添加方法时才加上,有点奇怪。
jQuery.extend( {
// Unique for each copy of jQuery on the page
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
// Assume jQuery is ready without the ready module
isReady: true,
error: function( msg ) {
throw new Error( msg );
},
noop: function() {},
isFunction: function( obj ) {
return jQuery.type( obj ) === "function";
},
isWindow: function( obj ) {
return obj != null && obj === obj.window;
},
isNumeric: function( obj ) {
// As of jQuery 3.0, isNumeric is limited to
// strings and numbers (primitives or objects)
// that can be coerced to finite numbers (gh-2662)
var type = jQuery.type( obj );
return ( type === "number" || type === "string" ) &&
// parseFloat NaNs numeric-cast false positives ("")
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
// subtraction forces infinities to NaN
!isNaN( obj - parseFloat( obj ) );
},
isPlainObject: function( obj ) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
return false;
}
proto = getProto( obj );
// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
},
isEmptyObject: function( obj ) {
/* eslint-disable no-unused-vars */
// See https://github.com/eslint/eslint/issues/6125
var name;
for ( name in obj ) {
return false;
}
return true;
},
type: function( obj ) {
if ( obj == null ) {
return obj + "";
}
// Support: Android <=2.3 only (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call( obj ) ] || "object" :
typeof obj;
},
// Evaluates a script in a global context
globalEval: function( code ) {
DOMEval( code );
},
// Convert dashed to camelCase; used by the css and data modules
// Support: IE <=9 - 11, Edge 12 - 13
// Microsoft forgot to hump their vendor prefix (#9572)
camelCase: function( string ) {
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},
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.0 only
trim: function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
},
// results is for internal usage only
makeArray: function( arr, results ) {
var ret = results || [];
if ( arr != null ) {
if ( isArrayLike( Object( arr ) ) ) {
jQuery.merge( ret,
typeof arr === "string" ?
[ arr ] : arr
);
} else {
push.call( ret, arr );
}
}
return ret;
},
inArray: function( elem, arr, i ) {
return arr == null ? -1 : indexOf.call( arr, elem, i );
},
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
merge: function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
for ( ; j < len; j++ ) {
first[ i++ ] = second[ j ];
}
first.length = i;
return first;
},
grep: function( elems, callback, invert ) {
var callbackInverse,
matches = [],
i = 0,
length = elems.length,
callbackExpect = !invert;
// Go through the array, only saving the items
// that pass the validator function
for ( ; i < length; i++ ) {
callbackInverse = !callback( elems[ i ], i );
if ( callbackInverse !== callbackExpect ) {
matches.push( elems[ i ] );
}
}
return matches;
},
// arg is for internal usage only
map: function( elems, callback, arg ) {
var length, value,
i = 0,
ret = [];
// Go through the array, translating each of the items to their new values
if ( isArrayLike( elems ) ) {
length = elems.length;
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
// Go through every key on the object,
} else {
for ( i in elems ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
}
// Flatten any nested arrays
return concat.apply( [], ret );
},
// A global GUID counter for objects
guid: 1,
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
var tmp, args, proxy;
if ( typeof context === "string" ) {
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.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = slice.call( arguments, 2 );
proxy = function() {
return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
now: Date.now,
// jQuery.support is not used in Core but other projects attach their
// properties to it so it needs to exist.
support: support
} );