extend
jQuery.extend = jQuery.fn.extend = function () { } / jQuery.extend({ })
- 把用户自定义的方法扩展到jQuery / jQuery.fn上 (也是合并:把自己传递进来对象中的方法合并到jQuery / jQuery.fn上)
- 把jQuery当做对象,扩展到其上面的的私有的属性和方法【完善类库】
$.extend({ xxx : function(){ console.log("XXX") }, }); $.xxx();
- 向其原型上扩展属性和方法,供实例对象(基于JQ选择器获取的结果)调取使用【写JQ插件】
$.fn.extend({ xxx(){ console.log("XXX") }, }); $(".box").xxx();
- 实现两个及多个对象的合并【浅比较浅合并 && 深比较深合并】
- 类似于Object.assign
- 传参类型:
- $ / $.fn.extend({})
- $.extend(true,{})
- $.extend({}, obj1, obj2)
- $.extend(true, {}, obj1, obj2)
分析JQ 源码中的jQuery.extend
jQuery.extend = jQuery.fn.extend = function () {
var options, name, src, copy, copyIsArray,copyIsObject, clone,
target = arguments[0] || {},//被替换对象
i = 1,
length = arguments.length,
deep = false;
//第一个值是布尔值,处理深浅合并
if (typeof target === "boolean") {
deep = target; //深合并
target = arguments[i] ||{}; //次项是被替换对象
i++;//为了获取替换对象
}
//target必须是对象
if (typeof target !== "object" && !isFunction(target)) target = {};
// 没有传递第二个对象,只是想把第一个传递的对象中的内容合并到$/ $.fn上
if (i === length) { //只传入一个对象
target = this; //target = $/ $.fn ,即合并原型或者对象上
i--; // ->此时i索引对应的是传递的那个对象
};
//target 代表最终要被替换的对象,最后返回target
// 接下来的循环就是拿到剩余的传递的对象,(可能是一个,也可能是多个),拿它们依次替换target
for (; i < length; i++) {
// options:每一轮循环中拿到的剩余的其中一个替换target的对象
options = arguments[i];
if (options == null) continue;
for (name in options) {
//copy 获取对象中的每一项,替换target中同名这一项
copy = options[name];
//获取的是对象的原型,被替换对象和替换对象相同,则忽略不作比较替换,退出当前循环,进行下一轮循环。[避免比较“套娃操作的对象”]
if (name === "__proto__" || target === copy) continue ;
var copyIsObject = jQuery.isPlainObject(copy),//是否为纯粹对象
copyIsArray = Array.isArray(copy);//是否为数组
// 深度合并options中的这一项需要是纯粹对象或者是数组对象,才有必要和target中对应的这一项进行深度对比,从而实现深度合并
if (deep && copy && copyIsObject || copyIsArray){
// copy代表的是options中的这一项, 对象/数组
// src 代表的是target 中的这一项[clone]
src = target[name];
if (copyIsArray && !Array.isArray(src)) {
// 如果options中的这一项是数组,但是target中的这一项不是数组,则让clone是一个数组
clone = [];
} else if (!copyIsArray && !jQuery.isPlainObject(src)) {
// 如果options中的这一项不是数组,且target中的这一项也不是纯粹对象,则让clone是一个空对象
clone = {};
} else {
//target中的这一项是数组或者对象时,clone等于这一项
clone = src;
};
copyIsArray = false;
// 基于递归的方式实现当前项的深度比较和深度合并
target[name] = jQuery.extend(deep, clone, copy);
//不需要深度合并的直接用options中的这一项替换target中的这一项即可
} else if (copy !== undefined) {
//值是undefined的时候是假删除状态,不再作合并处理
target[name] = copy;// 浅合并
};
};
};
return target;//返回被合并替换的对象(目标对象)
};
封装数组和对象深/浅合并插件merge
// 实现数组和对象深/浅合并
var merge = function merge() {
var options, target = arguments[0] || {}, i = 1,
length = arguments.length,
treated = arguments[length - 1], deep = false;
if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++;
};
//treated解决了jq源码中的也同样存在的递归溢出bug
if (Array.isArray(treated) && treated.treated) {
// 传递了记录已经处理过的内容的集合
// 后期循环的时候少循环处理一项
length--;
} else {
// 没传递,最后传递的这个值,不是用来存储处理过的内容,而是进行合并比较的
treated = [];
treated.treated = true;
}
//保证target必须是对象或者函数
if (typeof target !== "object" && !isFunction(target)) target = {};
//循环除target以外的剩下的对象
for (; i < length; i++) {
options = arguments[i];
//options不存在退出当前循环
if (options == null) continue;
// 防止死递归处理的操作
if (treated.indexOf(options) > -1) return options;
treated.push(options);
each(options, function (copy, name) {
var copyIsArray = Array.isArray(copy),//是否为数组
copyIsObject = isPlainObject(copy),//是否为纯对象
src = target[name],
clone = src;
if (deep && copy && (copyIsArray || copyIsObject)) {
if (copyIsArray && !Array.isArray(src)) clone = [];
if (copyIsObject && !isPlainObject(src)) clone = {};
target[name] = merge(deep, clone, copy, treated);
} else if (copy !== undefined) {
target[name] = copy;
}
});
}
return target;
};
插件中用到的 isPlainObject/ each 方法均来自utils.js(封装了一些常用的方法)。