前言
本文主要讲的不是如何使用jQuery的插件方法,而是jQuery.extend和jQuery.fn.extend的实现,这两种方法实现了拓展jQuery方法的途径,前者是增加jQuery的静态方法,也就是$.do()
这样;后者是增加jQuery的实例方法,也就是$(selector).do()
这样。这两者的用法分别为$.extend和$.fn.extend。如果想了解jQuery的插件开发可以看这里如何制作一个最简单的jQuery插件。
这两个方法最神奇的地方是居然用同一个函数来实现,其实从源码中可知this在里面起到了巨大的作用。要了解这两个方法的实现方式,就要先从方法的实现效果去反推和理解。
核心源码
// jQuery插件拓展
195 jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
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++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
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 : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
262 };
思路分析
- 简单目标:对jQuery和jQuery.fn方法的拓展,也就是
$.do()
和$().do()
。 - 中等目标:把所有的对象合并到第一个对象target上面(如果只有一个对象就合并到自己身上,等于没有合并),并将target返回到方法外。
- 高级目标:深拷贝,也就是说把每个对象中的所有对象的属性都拷贝到第一个对象身上,从object原型继承的属性会被拷贝,值为undefined的属性不会被拷贝。$.extend()的深拷贝和浅拷贝详细讲解
源码分析
1)明确target是哪个对象
// 是用户传进来的第一个参数,如果不填,则新建一个对象(不能为空)
var target = arguments[0] || {};
// 如果第一个参数类型为boolean,则target转为第二个参数,如果不填,则新建一个对象(不能为空)
if (typeof target === "boolean") {
target = arguments[1] || {};
}
// 如果第一个参数不是对象或方法(也就有可能为字符串或其他),则将target转换成空对象
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
}
// 如果只有一个参数,表示是 jQuery 静态方法
if (length === i) { // length = arguments.length
target = this; // this指jQuery本身
// 所以$.extend和$.fn.extend可以写在一起,就是由这个this决定的
}
// 最后的target一定是对象,要么是用户传进来的对象,要么是空对象{}
2)复制所有对象的属性到第一个对象target的属性中,如果有相同属性名,将会覆盖target的该属性名的值
// 如果target=this,则循环 1 次,返回原本的target
// 如果target!=this,则循环 n-1 次,n为obj的数量(包括target本身)
for (; i < length; i++) {
// 取出每个对象的key,如果用es6,可以直接用for-of
options = arguments[i]
for (name in options) {
if () {}
else if (copy !== undefined) {
target[name] = copy;
}
}
}
3)深克隆,将所有对象的属性复制到target中,如果属性不是对象,则完全覆盖;如果是纯对象或者是数组,则不完全覆盖,只覆盖对象或数组里面的相同属性。
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;
}
最后总结
看完源码,感觉jQuery的extend方法太强大了,一个方法里面主要实现了两个目标,一是帮助jQuery和jQuery的实例增加方法,二是克隆对象。前者只能有一个参数,而后者可能有无限个参数。前者靠target = this
来实现,后者靠 递归 来实现。总之,用85行的代码(含注释和空行)就能同时实现两个功能,既强大又简洁,完全符合了jQuery的 write less, do more. 的追求。