文 / 景朝霞
来源公号 / 朝霞的光影笔记
ID / zhaoxiajingjing
目录:0 / extend的应用1 / 简单实现extend方法2 / 应用:深浅比较的对象合并3 / 对象的深浅合并(1)浅合并 shallowMerge(2)深合并 deepMerge(3)优化深合并4 / 深浅克隆(1)浅克隆 shallowClone(2)深克隆 deepClone5 / JQ中的extend源码
0 / extend的应用
extend 给JQ的原型和对象扩展方法
① $.extend({xxx:function....})
向JQ对象上扩展方法【工具类的方法=>用于完善类库】
② $.fn.extend({xxx:function.....})
向JQ的原型上扩展方法【供实例调用=>JQ插件】
$.extend({ MySay:function(){ // 这里的THIS --> jQuery console.log('miao~'); } }); $.MySay(); $.fn.extend({ MyEat:function(){ // 这里的THIS --> JQ的实例对象 console.log('炸鸡~'); } }); $('body').MyEat();
△ extend应用
1 / 简单实现extend方法
extend 方法做的事情:
把对象上的属性一一的追加到jQuery对象,或者jQuery.fn上,即:追加到this上
jQuery.extend = jQuery.fn.extend = function (obj) { // 容错 if (obj == null || typeof obj !== 'object') throw new TypeError('obj must be an object!'); var self = this, keys = Object.keys(obj); typeof Symbol !== 'undefined' ? keys = keys.concat(Object.getOwnPropertySymbols(obj)) : null; keys.forEach(function (key) { self[key] = obj[key]; }); return self; };
△ 最简版的extend的实现
扩展方法,就是对象的合并
但,jQuery还提供了:深比较or浅比较的合并
2 / 应用:深浅比较的对象合并
JQ的extend另一个点:基于浅比较or深比较,来实现对象的合并
① $.extend(obj1, obj2)
浅合并:obj2替换obj1,最后返回的是obj1,类似于之前说的Object.assign的功能
② $.extend(true, obj1, obj2)
深合并:obj2替换obj1,最后返回的是obj1
let obj1 = { name:'朝霞的光影笔记', friends:{ 0:'小汤圆' } }; let obj2 = { name:'公众号:朝霞的光影笔记', friends:{ 1:'江小虾' }, id:'zhaoxiajingjing' }; console.log(Object.assign(obj1, obj2));
△ 需要合并的参数
△ 图1_Object.assign的浅合并
△ 图2_JQ中的深浅比较,合并对象
好啦,咱之前写过的那个方法库utils.js
那咱就继续往上面扩展:深浅合并、深浅克隆吧~
3 / 对象的深浅合并
深浅合并:主要是针对对象数据类型值和其他数据类型值
(1)浅合并 shallowMerge
对象的浅合并,合并规律:
A:obj1,B:obj2
① A不是对象,B是对象:B替换A
② A不是对象,B不是对象:B替换A
③ A是对象,B不是对象:依旧A为主
④ A是对象,B是对象:迭代B,替换A
var shallowMerge = function shallowMerge(obj1, obj2) { var isPlain1 = isPlainObject(obj1), isPlain2 = isPlainObject(obj2); // A不是对象,B是或者不是对象:都以B为主 if(!isPlain1) return obj2; // A是对象,B不是对象:以A为主 if(!isPlain2) return obj1; // A/B是对象:迭代B,替换A each(obj2, function(key, value){ obj1[key] = value; }); return obj1; };
△ 请自行写到utils.js里面~浅合并
(2)深合并 deepMerge
对象的深合并,合并规律:
A:obj1,B:obj2
① A和B只要有一个不是对象,那么就是浅合并
② A和B都是对象
=> 循环B时,A和B中的某个属性又是对象,那么需要递归合并
var deepMerge = function deepMerge(obj1, obj2) { var isPlain1 = isPlainObject(obj1), isPlain2 = isPlainObject(obj2); // A、B只要有一个不是对象,就浅合并 if (!isPlain1 || !isPlain2) return shallowMerge(obj1, obj2); // A和B都是对象 each(obj2, function (key, value) { obj1[key] = deepMerge(obj1[key], value); }); return obj1; };
△ 深合并
(3)优化深合并
如果出现循环引用的情况,深合并怎么处理呢?
let obj1 = { name: '朝霞的光影笔记', friends: { 0: '小汤圆' } }; obj1.A = obj1; //=>【循环引用】 let obj2 = { name: '公众号:朝霞的光影笔记', friends: { 1: '江小虾' }, id: 'zhaoxiajingjing' }; obj2.A = obj2; //=>【循环引用】
△ 需要合并的数据
深度合并:标记,只要处理过的,就不再处理了
var deepMerge = function deepMerge(obj1, obj2, cache) { // 容错:防止循环引用导致了死递归问题 cache = !Array.isArray(cache) ? [] : cache; if (cache.indexOf(obj2) >= 0) return obj2; cache.push(obj2); var isPlain1 = isPlainObject(obj1), isPlain2 = isPlainObject(obj2); // A、B只要有一个不是对象,就浅合并 if (!isPlain1 || !isPlain2) return shallowMerge(obj1, obj2); // A和B都是对象 each(obj2, function (key, value) { obj1[key] = deepMerge(obj1[key], value, cache); }); return obj1; };
△ 循环引用
循环引用:在WEB浏览器中,window.window
△ 图3_window的循环引用
4 / 深浅克隆
对象/数组浅克隆:
1、...
2、迭代
3、内置方法,如:slice
let obj = {gzh:{id:'zhaoxiajingjing'}}; let obj2 = { ...obj }; let obj3 = {}; for(let key in obj){ obj3[key] = obj[key]; } let arr2 = [{id:'zhaoxiajingjing'}, 100].slice();
深克隆:JSON.parse(JSON.stringify(obj))
变为字符串,再变为对象,内存地址会重新开辟一下。但是,转为字符串并不是所有的值都支持:
△ 图4_JSON.parse/JSON.stringify
在我现在的项目中,由于没有这么复杂的数据,都是对象的属性值是:number/boolean/string/纯粹对象/数组这样的数据,所以直接使用的就是JSON.parse(JSON.stringify())
但是,对于更复杂的数据,还需要咱写个方法好好的处理
对象的属性值可以是任意类型的值,那么,深浅克隆,就是对JS数据类型值的克隆
(1)浅克隆 shallowClone
let obj = { a:1, str:'miao~', b:true, c:null, 0:undefined, f:function(){}, e:/^\d+$/, d:new Date(), s:Symbol() }; obj.A = obj; let arr = [10,[2], {id:'zhaoxiajingjing'}];
浅克隆:
1、 基本数据类型值
① string、number、boolean、null、undefeind 直接返回就可以了
② symbol和bigint 不能通过new的方式创建实例对象,但是可以Object(xxx)来返回对应的数据类型
2、引用数据类型值
① 函数数据类型值:直接返回一个匿名函数过去,然后在这个匿名函数中调用之前的那个函数,并指定this和传递参数
② 对象数据类型值,常见的有:
=> 日期对象、正则对象:直接再new一个新的返回就可以
=> error对象:直接new一个新的,并把错误信息传过去
=> 纯粹对象(原型链直接指向Object基类)、数组对象:new一个新的实例,再循环遍历去赋值
var shallowClone = function shallowClone(obj) { var type = toType(obj), Ctor = null; // null、undefined 直接返回 if (obj == null) return obj; // symbol\bigint if (/^(symbol|bigint)$/i.test(type)) return Object(obj); // 函数数据类型 if (/^function$/i.test(type)) { return function anonymous() { return obj.apply(this, arguments); }; } Ctor = obj.constructor; // 日期对象、正则对象 if (/^(date|regexp)$/i.test(type)) return new Ctor(obj); // error 对象 if (/^error$/i.test(type)) return new Ctor(obj.message); // 纯粹对象和数组:循环解决 if (isPlainObject(obj) || type === 'array') { var restul = new Ctor(); each(obj, function (key, value) { result[key] = value; }); return result; } // 以上的都不是:① 剩下的基本数据类型值 ②…… return obj; };
△ 浅克隆
(2)深克隆 deepClone
深克隆:
1、只要不是纯粹对象和数组,其他的都浅克隆即可
2、纯粹对象和数组对象:
① 他们的值有可能是以上说到的任何一种数据类型,那么需要递归深克隆
② 防止循环调用时产生死递归现象
var deepClone = function deepClone(obj, cache) { var type = toType(obj), Ctor = null, result = null; // 不是纯粹对象和数组,都去浅克隆就行了 if (!isPlainObject(obj) || type !== 'array') return shallowClone(obj); // 纯粹对象和数组对象 // 防止死递归 cache = Array.isArray(cache) ? cache : []; if(cache.indexOf(obj) >= 0) return obj; cache.push(obj); Ctor = obj.constructor; result = new Ctor(); each(obj, function (key, value){ result = deepClone(value, cache); }); return result; };
5 / JQ中的extend源码
大家记得把深浅合并和深浅克隆放到utils.js里面
浅合并原则:主要针对 对象和其他数据类型值
1、A不是对象,不管B是不是对象:B替换A
2、A是对象,B不是对象:依旧是A
3、A是对象,B是对象:迭代B,替换A
深合并原则:
1、A和B只要有一个不是对象,那就是浅合并
2、A和B都是对象:
① 循环B时,需要去递归查看下A和B中的值是否也是对象
② 循环引用时,避免死递归
浅克隆原则:克隆是对 数据类型值 的克隆
1、基本数据类型值
① string\boolean\number\null\undefined,直接返回就可以了
② symbol\bigint,返回Object(xxxx)就可以了
2、引用数据类型值
① 函数数据类型值:返回一个匿名函数,在匿名函数里面去调用原来的函数,并记得修正THIS和传值
② 对象数据类型值
=> 日期对象、正则对象:new一个新的实例对象返回去就行了
=> error对象:new一个新的实例对象回去,记得传递错误的信息
=> 纯粹对象和数组对象:创建一个新的实例对象、循环遍历赋值后返回即可
深克隆原则:
1、只要不是纯粹对象和数组对象,都去浅克隆就行了
2、纯粹对象和数组对象:创建一个新的实例对象
① 循环遍历得到值时,需要递归克隆该值
② 循环引用时,需要缓存一下,不要出现死递归现象
JQ的extend方法的作用:
1、向jQuery、jQuery.fn上扩展方法:
jQuery.extend({A:function....})
jQuery.fn.extend({B:function....})
2、深浅合并:
浅合并:$.extend(obj1, obj2)
深合并:$.extend(true, obj1, obj2)
下面是直接从JQ中粘贴出来的源码:
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" && !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) { copy = options[name]; // Prevent Object.prototype pollution // Prevent never-ending loop if (name === "__proto__" || target === copy) { continue; } // Recurse if we're merging plain objects or arrays if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { src = target[name]; // Ensure proper type for the source value if (copyIsArray && !Array.isArray(src)) { clone = []; } else if (!copyIsArray && !jQuery.isPlainObject(src)) { clone = {}; } else { clone = src; } copyIsArray = false; // 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; };
- end -
从"你"到"更好的你",有无限可能~