之前一直没怎么耐心看过源码,决定详细阅读几个知名库的源码。今天读了 jQuery extend 源码后,发现实现的确实精妙。
接下来先看看 extend 的用法。
extend 用法
jQuery 里的 extend 主要有下面几种用法:
- 拷贝对象
$.extend({}, { name: "zs" }, { age: 12 });
// {name:"zs", age:12}
$.extend([], [1, 2, 3], [4, 5, 6, 7]);
// [4, 5, 6, 7];
- 第一个参数为 true,表示深拷贝
$.extend(
true,
{},
{ name: "zs", child: { name: "lisi" } },
{ child: { age: 12 } }
);
// {name:'zs',child:{name:'lisi', age:'12'}}
- 扩展静态方法
$.extend({
show() {}
});
$.show();
- 扩展原型上的方法
$.fn.extend({
show() {}
});
$("div").show();
extend 核心技巧
看完上面的用法之后,再去看 extend 的源码就简单了。它最核心的技巧就是:参数规格化。也就是保证 extend 方法 调用方式是一致的。
我们来看看上面几个使用方法,实际上 extend 源码将它们进行了统一处理。都转成了下面的形式来调用:
// deep 表示是否深拷贝
// target 表示目标对象
// copy 表示要拷贝的对象
extend(deep, target, copy);
再结合下面示例,相信就明白了。
// 1
$.extend({}, { name: "zs" }, { age: 12 });
// 转成了
$.extend(false, {}, { name: "zs" }, { age: 12 });
// 2
$.extend({
show() {}
});
// 转成了
// 这里 this,就是 jQuery 对象
$.extend(false, this, {
show() {}
});
// 3
$.fn.extend({
show() {}
});
// 转成了
// 这里 this 就是 jQuery.prototype 对象
$.fn.extend(false, this, {
show() {}
});
总结
相信看完上面,再去看看源码,你已经懂了。最后附上注释:
var isFunction = function isFunction(obj) {
// 旧 chrome firefox 浏览器,`typeof html元素` 会返回 "function"
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
/**
* 本质是将所有可能全部转成 extend(deep, target, copy) 形式
*/
jQuery.extend = jQuery.fn.extend = function() {
var options,
name,
src,
copy,
copyIsArray,
clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// 处理深拷贝, extend(deep, target,...)
if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++; // 从下一个开始循环
}
// 如果不是对象,即 extend(1,{name:'hi'}) 或者 extend(true, 1, {name:'hi'})时,保证 target 是对象
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}
// 将 jQuery.fn 当作是 target,让对象拷贝到它身上
// 即转成 extend(false, this, {css(){}})
if (i === length) {
target = this;
i--;
}
for (; i < length; i++) {
// 只处理非 null/undefined 值,即 extend(a,b,null) 里的 null 会跳过
if ((options = arguments[i]) != null) {
// 从 target 的下一个开始循环,extend(target, {name:1})
// 循环数组或对象
for (name in options) {
copy = options[name]; // 拷贝的对象
// 防止 Object.prototype 污染
// target === copy 同一个对象,实际不需要拷贝
if (name === "__proto__" || target === copy) {
continue;
}
// 递归处理对象和数组,深拷贝
if (
deep &&
copy &&
(jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
) {
src = target[name]; // 要输出的值
// 始终保持阵型 extend(deep, a, b)
// 如果 copy 是数组,src 不是数组 , extend([])
if (copyIsArray && !Array.isArray(src)) {
clone = [];
// 如果 copy 不是数组,src 不是对象 extend([], {})
} else if (!copyIsArray && !jQuery.isPlainObject(src)) {
clone = {};
} else {
clone = src;
}
copyIsArray = false; // copy不是数组
// extend 永远保持这种三个参数的形式执行,进行拷贝
target[name] = jQuery.extend(deep, clone, copy);
// 不拷贝 undefined 值,如[undefined] 变成 [empty],而 {a:undefined} 不拷贝 a 属性
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// 返回修改后的 target 对象
return target;
};
jQuery.extend({
isPlainObject: function(obj) {
return typeof obj === "object" && obj != null;
}
});