写在最前面
之前看了阮大佬的博客并且自己分析了一波,感觉意犹未尽,于是趁热自己抽时间又看了下Jquery中的extend方法源码,然后写写自己的理解。
预备工作
在看这个方法之前,我先看了下它的使用方法(jquery中文文档):

知道怎么用就可以开始看看源码啦!
喵一下源码
jquery.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;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
//-------------------三脸懵逼
// 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 (length === i) {
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;
}
长还是挺长的,咱们已经分好段了,接下来一段段的看就好了
一脸懵逼
var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
一般来说,开头都是进行初始化什么的,jq也不例外,第一句除了target = arguments[0] || {}
以外,都是提前对变量进行声明,这是个好习惯!
target = arguments[0] || {}
做了啥?我们知道或运算符 || 会进行判断,如果存在arguments[0]就将其值赋值给target,如果没有就将target声明成一个空对象。
二脸懵逼
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
先看看英文注释,用于深拷贝的预处理。从前面我们知道,extend有两种使用方法,不记得在回去看看哟。。第二种就是第一个参数为boolean,标志着深拷贝。
进入这一步之前target是extend的第一个参数,对target进行简单的类型判断,如果是Boolean类型,说明是使用的第二种方法。
- 首先我们用deep将这个boolean标志存下来
- 那么target此时肯定不能指向第一个参数,于是将target指向第二个参数:
target = arguments[1] || {}
- 这里将i = 2,i是啥?现在还不知道它的作用,先不管。
三脸懵逼
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
}
到了这一步,我们知道此时target指向第一个参数,也就是我们目标对象。等等...我们现在只知道指向的是arguments[1]并没有告诉我们这是对象,于是这里就是进一步对target的身份进行核实,你是对象这里就不会走,不是对象,抱歉!你需要变成一个对象。。
四脸懵逼
// extend jQuery itself if only one argument is passed
if (length === i) {
target = this;
--i;
}
- 如果是第一种
$.extend(obj1, obj2, ...)
的话,length = 2... 这里i = 1 - 如果是第二种
$.extend(boolean, obj1, obj2)
的话,length = 3... 这里i = 2
这样看来并不会存在length === i 的这种情况,jq写错了吗?其实并没有。。如果你仔细的浏览了下文档机会发现这样的一句话

也就是说参数个数为1的时候,这个if判断是成立的,将this传给了target,这个时候我们的目标对象就是jq本身。所以结合文档看源码还是挺有用的,不然真的会云里雾里的。
在这里--i之后,i的值变成0。(extend参数个数为1的情况下)
到这里,我们有个大胆的想法,这个i会不会就是target之后的对象的下标位置呢?
例如:
$.extend(obj1, obj2, obj3, obj4)
target = arguments[0]
length = 4
i = 1 //obj2的下标
$.extend(true, obj1, obj2, obj3, obj4)
target = arguments[1]
length = 5
i = 2 //obj2的下标
$extend(obj1)
target = this //jq本身
length = 1
i = 0 //obj1的下标
在三种情况下,我们得到不同的三个i,到这里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;
}
}
}
}
这一大段无非就是个拷贝嘛。。先别害怕。这么一大段for循环,就是对target后面参数对象进行遍历,分别将每个参数对象中的属性,拷贝到target对象中。知道了这个咱们接着看下去。
- 首先进行了if判断
(options = arguments[i]) != null
这个参数是不是null,为null就跳过继续下一个。 - 不为null就对该参数对象中的每个属性进行拷贝,这里有一点要提一下
// Prevent never-ending loop
if (target === copy) {
continue;
}
这里主要是担心参数对象的属性就是target,例如
obj2 = {'obj1': obj1}
$.extend(obj1, obj2)
在这种情况下,会陷入死循环,这个地方,理解一下就懂了,操作的是同一个内存(对象)。
除了这一点,下面一大段就是进行类型判断然后进行赋值。配合我上一篇文章:JavaScript面向对象之三(非构造函数继承)中的深拷贝继承一起食用更佳。
还懵逼吗?
看了源码,最大的感受就是,这种经典的库,考虑真的很周全很仔细,尤其是那块死循环的判断,刚开始没理解,最后恍然大悟,理解完浑身畅快的感觉真的很棒。喝口水,切图去...哈哈。