ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 浅拷贝/Shallow Copy 浅拷贝指拷贝了引用值。 ~~~ var original = {"prop1" : "Prop1", "prop2" : "prop2"}; console.log(JSON.stringify(original)); // {"prop1" : "Prop1", "prop2" : "prop2"} var shallowCopy = original; console.log(JSON.stringify(shallowCopy)); // {"prop1" : "Prop1", "prop2" : "prop2"} shallowCopy.prop1 = "ChangedProp1"; console.log(JSON.stringify(original)); // {"prop1" : "ChangedProp1", "prop2" : "prop2"} console.log(JSON.stringify(shallowCopy)); // {"prop1" : "ChangedProp1", "prop2" : "prop2"} ~~~ ![](https://box.kancloud.cn/27d85736b58aeeab038ce323611a5d91_640x480.png) 注意: * 浅拷贝中,原始值和副本共享同样的属性。 * 浅拷贝只拷贝了对象引用。 * 浅拷贝中如果修改了拷贝对象会影响到原始对象,反之亦然。 * js中,数组和对象的赋值默认为浅拷贝。 ## 深拷贝/Deep Copy 深拷贝指递归的复制对象的属性给新对象。jquery中我们使用$.extend去进行深拷贝。 ~~~ $.extend(deepCopy, target, object1, [objectN] ) ~~~ 第一个参数传入true,指明此为深拷贝,target为目标对象,object1,原对象。 ~~~ var original = {"prop1" : "Prop1", "prop2" : "prop2"}; console.log(JSON.stringify(original)); // {"prop1" : "Prop1", "prop2" : "prop2"} var deepCopy = $.extend(true, {}, original); console.log(JSON.stringify(deepCopy)); // {"prop1" : "Prop1", "prop2" : "prop2"} deepCopy.prop1 = "ChangedProp1"; console.log(JSON.stringify(original)); // {"prop1" : "Prop1", "prop2" : "prop2"} console.log(JSON.stringify(deepCopy)); // {"prop1" : "ChangedProp1", "prop2" : "prop2"} ~~~ ![](https://box.kancloud.cn/954bac2fe5ee4c462a45fc6a481da707_640x480.png) ### 实现深拷贝 1.展开方法 ``` var obj = { name: "yc", age:40 } // var yc = obj var yc = {...obj}; yc.name = "zhang"; console.log(yc,obj); ``` 2.parse stringify ``` var copyObj = { name: 'ziwei', arr : [1,2,3] } var targetObj = JSON.parse(JSON.stringify(copyObj)) 此时 copyObj.arr !== targetObj.arr 已经实现了深拷贝 ``` 注意: * 深拷贝中,副本和原对象不共享属性 * 深拷贝递归的复制属性 * 深拷贝的副本不会影响到原对象,反之亦然 * js中所有的原始数据类型默认执行深拷贝,比如Boolean, null, Undefined, Number,String等 译者注: * 实际工作中,通常所谓clone一个对象一般都是指深拷贝,即拷贝一个对象的副本。 * 有些时候一层的深拷贝被认为是浅拷贝,比如shallowCopy({a:1,b:{hello:'world}}),b的值是一个对象,浅拷贝出来的新对象直接引用了原对象的value对象,所以也会相互影响的。 * 实际工作中,深拷贝(递归的对对象里每一层值和对象进行拷贝)应用很广泛,我认为浅拷贝只变成了一种概念,因为每一个需要clone的地方都是深拷贝。 * 复制一个副本对象也可以通过工厂函数,比如vuejs里的data [参考](https://segmentfault.com/a/1190000016697476) ### 1.深拷贝、浅拷贝是什么 > 我们讨论JS对象深拷贝、浅拷贝的前提 只有对象里嵌套对象的情况下,才会根据需求讨论,我们要深拷贝还是浅拷贝。 比如下面这种对象 ~~~ var obj1 = { name: 'ziwei', arr : [1,2,3] } ~~~ 因为,如果是类似这样{name: 'ziwei'},没有嵌套对象的对象的话,就没必要区分深浅拷贝了。只有在有嵌套的对象时,深拷贝和浅拷贝才有区别 > 浅拷贝是什么样子的 (我们暂时不管具体如何实现,因为下面会单讲) 调用shallowCopy()后,obj2拷贝obj1所有的属性。但是obj2.arr和obj1.arr是不同的引用,指向同一个内存空间 ~~~ var obj2 = shallowCopy( obj1 , {}) console.log( obj1 !== obj2 ) // true 无论哪种拷贝,obj1和obj2一定都是2个不同的对象(内存空间不同) console.log( obj2.arr === obj1.arr ) // true 他们2个对象里arr的引用,指向【相同的】内存空间 ~~~ ***所以, 2个obj经过拷贝后,虽然他们属性相同,也的确是不同的对象,但他们内部的obj都是指向同一个内存空间,这种我们叫浅拷贝*** > 深拷贝是什么样子的 (我们暂时不管具体如何实现,因为下面会单讲) 调用deepCopy()后,obj2拷贝obj1所有的属性,而且obj2.arr和obj1.arr是指向不同的内存空间, 2个obj2除了拷贝了一样的属性,没有任何其他关联。 ~~~ var obj2 = deepCopy( obj1 , {}) console.log( obj1 !== obj2 ) // true 无论哪种拷贝,obj1和obj2一定都是2个不同的对象(内存空间不同) console.log( obj2.arr === obj1.arr ) // false 他们2个对象里arr的引用,指向【不同的】内存空间 ~~~ ***所以, 2个obj经过拷贝后,除了拷贝下来相同的属性之外,没有任何其他关联的2个对象,这种我们叫深拷贝*** ### 2.深拷贝在业务里的最常见的应用场景 举个栗子,业务需求是 : 一个表格展示商品各种信息,点击【同意】时,是可以弹出对话框调整商品数量的。 这种业务需求下,我们就会用到对象的深拷贝。因为【商品表格】的属性和【调整商品表格】的属性几乎一样,我们需要拷贝。 下面的伪代码和图片就是展示使用浅拷贝存在的问题 ![图片描述](https://segmentfault.com/img/bV1Zpx?w=1364&h=486 "图片描述") 这样得到的adjustTableArr和tableArr里,内部对象都是相同的,所以就出现了图中红线标注的情况, 当我们修改【调整商品表格】里的商品数量时,【商品表格】也跟着改变了,这并不是我们想要的 ~~~ // 表格对象的数据结构 var tableArr = [ {goods_name : '长袖腰背夹' , code : 'M216C239E0864' , num : '2'}, {goods_name : '长袖腰背夹' , code : 'M216C240B0170' , num : '3'}, {goods_name : '短塑裤' , code : 'M216D241C04106' , num : '3'}, ] var adjustTableArr = [] // 调整表格用的数组 for (var key in tableArr) { // 浅拷贝 adjustTableArr[key] = tableArr[key] } ~~~ 而实际上,我们希望这2个表格里的数据完全独立,互不干扰,只有在确认调整之后才刷新商品数量。 这种情况下我们就可以使用前面说的深拷贝的一行黑科技 ~~~ var adjustTableArr = JSON.parse(JSON.stringify(tableArr)) ~~~ 还记得它的缺陷吗? 对象里的函数无法被拷贝,原型链里的属性无法被拷贝。这里就对业务没有影响,可以很方便的深拷贝。 ### 3.深拷贝和浅拷贝的实现方式 其实JQ里已经有$.extend()函数,实现就是深拷贝和浅拷贝的功能。有兴趣的小伙伴也可以看看源码。 > 浅拷贝 浅拷贝比较简单,就是用for in 循环赋值 ~~~ function shallowCopy(source, target = {}) { var key; for (key in source) { if (source.hasOwnProperty(key)) { // 意思就是__proto__上面的属性,我不拷贝 target[key] = source[key]; } } return target; } ~~~ > 深拷贝的实现 * 深拷贝,就是遍历那个被拷贝的对象 * 判断对象里每一项的数据类型 * 如果不是对象类型,就直接赋值,如果是对象类型,就再次调用deepCopy,递归的去赋值。 ~~~ function deepCopy(source, target = {}) { var key; for (key in source) { if (source.hasOwnProperty(key)) { // 意思就是__proto__上面的属性,我不拷贝 if (typeof(source[key]) === "object") { // 如果这一项是object类型,就递归调用deepCopy target[key] = Array.isArray(source[key]) ? [] : {}; deepCopy(source[key], target[key]); } else { // 如果不是object类型,就直接赋值拷贝 target[key] = source[key]; } } } return target; } ~~~ 以上的无论深、浅拷贝,都用了source.hasOwnProperty(key),意思是判断这一项是否是其自有属性,是的话才拷贝,不是就不拷贝。 也就是说\_\_proto\_\_上面的属性,我不拷贝。这个其实你可以根据业务需求,来决定加上和这个条件 (JQ的$.extend()是会连\_\_proto\_\_上的属性也拷贝下来的,但是是直接拷贝到对象上,而不是放到之前的\_\_proto\_\_上) ### 4.总结与建议 虽然大家可能经常用框架提供的api来实现深拷贝。 这篇文章分享的目的,更多还是希望用一篇文章整理清楚深浅拷贝的含义、递归实现思路,以及小伙伴们如果使用了JSON.parse()这种黑科技,一定要清楚这样写的优缺点。 ### 5.修正 上面的deepCopy方法有漏洞,没有考虑source一开始就是数组的情况 下面是一个修改后版本 ~~~ function deepCopy( source ) { let target = Array.isArray( source ) ? [] : {} for ( var k in source ) { if ( typeof source[ k ] === 'object' ) { target[ k ] = deepCopy( source[ k ] ) } else { target[ k ] = source[ k ] } } return target } ~~~