最近在学习jQuery源码(v2.0.3),故在此跟大家一起分享下学习的经验和一些小小技巧,jQuery的扩展方法extend是我们经常在写插件的时候常用的方法,extend方法有两种,一种是$.extend(),另外一种是$.fn.extend()。两种方法有少许差异,再次,我们一起去学习了解。
jQuery的扩展方法原型
extend(dest, src1, src2, src3...)
extend的含义是讲src1,src2,src3...合并到dest中,返回值为合并后的dest,从上面可以看出使用了extend方法后,会修改dest的结构,同时也可以将空对象{}作为参数传入,即使用如下:
var newSrc = $.extend({}, {name: 'ccc', age: 21}, {name: 'xxx, sex: 'Boy'});
可以得到合并后的结果如下所示:
newSrc = {name: 'xxx', age: 21, sex: 'Boy'};
可以看出如果多个合并对象中有相同属性的将会进行合并,并且后来合并的属性会覆盖前面的属性。
省略dest参数
同时extend方法的dest参数可以省略,如果省略了dest参数,那么extend方法只能拥有一个参数,要不然会报错,只有一个参数的时候是将该src合并到调用extend方法的对象中去,如下所示:
1、$.extend(src)
该方法就是将src合并到jQuery的全局对象中去,如:
$.extend({
name: function () {
console.log('my name is chuanzhushen');
}
});
就是将name方法合并到jquery的全局对象中去,既可以通过$.name()或者jQuery.name()来调用此方法。
2、$.fn.extend(src)
该方法将src合并到jQuery的实例对象中去,如:
$.fn.extend({
name: function () {
console.log('my name is scienceswork');
}
});
就是将name方法合并到jQuery的实例对象上去,该方法与$.extend()的区别在于需要实例化后才能调用该方法,如:
$().name(); // my name is scienceswork
$.name(); // my name is chuanzhushen
extend方法的重载原型
extend(boolean, dest, src1, src2, src3...)
第一个参数boolean代表是否进行深拷贝,其余参数与之前的相同,JavaScript里有深拷贝和浅拷贝,我们先来了解一下什么是深拷贝,什么是浅拷贝。
var result = $.extend(true, {},
{name: 'ccc', location: {city: 'guangzhou', county: 'cn'}},
{last: 'sciences', location: {state: 'xxx', county: 'cn'}}
)
从上面的例子中我们可以看出src1中嵌套子对象location:{city: 'guangzhou'},src2中也嵌套了对象localtion:{state: 'xxx'},第一个深度拷贝的参数我们发现为true,那么合并后的结果就是:
result = {
name: 'ccc',
last: 'sciences',
location: {
city: 'guangzhou',
state: 'xxx',
county: 'cn'
}
};
上面即是使用浅拷贝进行合并的结果,其嵌套的对象不会进行深度遍历拷贝。
源码阅读
jQuery(v2.0.3)关于extend方法的源码从285行~347行,我们将其拷贝上来,并且做了简单的注释,对其进行一个分析:
jQuery.extend = jQuery.fn.extend = function() {
// target = arguments[0] || {}表示是否有深度拷贝参数,如果没有的话就为空对象
// length表示传入参数的长度,默认的深度拷贝参数deep为false
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// 判断是否为深度拷贝参数,如果是的话赋值给deep,并且设置拷贝目标为arguments[1]
// 且修改类数组的长度i为2
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// 判断传入的拷贝参数是否为对象或者函数,如果不是则直接设置为空对象{}
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// 判断参数长度是否等于i
if ( length === i ) {
target = this;
--i;
}
// 使用for循环进行拷贝
for ( ; i < length; i++ ) {
// 如果传入的拷贝参数为null,则不进行处理
if ( (options = arguments[ i ]) != null ) {
// 对被拷贝的对象进行遍历
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// 对target和copy进行判断,防止死循环
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 );
// 如果拷贝的对象为undefined则不进行拷贝
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// 返回对象
return target;
};
从上可以看出,在内部使用了jQuery.extend = jQuery.fn.extend,在这里可以看出$.extend和$.fn.extend是一样的,为什么一个是静态方法,一个必须实例化后才能调用方法,在源码的第96行,可以看到使用了如下:
jQuery.fn = jQuery.prototype = {...}
表明jQuery将原形prototype赋值给了jQuery.fn,这也能表明为什么一个是静态方法,另外一个必须实例化对象后才能使用。部分解释已经写在注释里了,由于第一次解读源码,对源码的熟悉程度也没从整体上进行把握,欢迎大家来吐槽。一起学习一起进步。
接下来还会对jQuery源码进行其他方面的阅读,阅读源码是一个不错的学习方式,有志同道合的童鞋可以一起学习。