写在最前面
之前看了阮大佬的博客并且自己分析了一波,感觉意犹未尽,于是趁热自己抽时间又看了下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 = 4i = 1 //obj2的下标复制代码
$.extend(true, obj1, obj2, obj3, obj4)target = arguments[1]length = 5i = 2 //obj2的下标复制代码
$extend(obj1)target = this //jq本身length = 1i = 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) 复制代码
在这种情况下,会陷入死循环,这个地方,理解一下就懂了,操作的是同一个内存(对象)。
除了这一点,下面一大段就是进行类型判断然后进行赋值。配合我上一篇文章:中的深拷贝继承一起食用更佳。
还懵逼吗?
看了源码,最大的感受就是,这种经典的库,考虑真的很周全很仔细,尤其是那块死循环的判断,刚开始没理解,最后恍然大悟,理解完浑身畅快的感觉真的很棒。喝口水,切图去...哈哈。