js深拷貝和淺拷貝的深入講解
淺拷貝
創(chuàng)建一個(gè)新的對(duì)象,來(lái)接受你要重新復(fù)制或引用的對(duì)象值。如果對(duì)象屬性是基本的數(shù)據(jù)類型,復(fù)制的就是基本類型的值給新對(duì)象;但如果屬性是引用數(shù)據(jù)類型,復(fù)制的就是內(nèi)存中的地址,如果其中一個(gè)對(duì)象改變了這個(gè)內(nèi)存中的地址,會(huì)影響到另一個(gè)對(duì)象。

實(shí)現(xiàn)方法
方法一:Object.assign
es6 中 object 的一個(gè)方法,用于 JS 對(duì)象的合并等,返回目標(biāo)對(duì)象。它不會(huì)拷貝對(duì)象的繼承屬性和不可枚舉的屬性
let target = {};
let source = {a:{b:1}};
Object.assign(target,source)
console.log(taget) // {a:{b:1}}
target.a.b = 2;
console.log(taget) // {a:{b:2}}
console.log(source) // {a:{b:2}}方法二:擴(kuò)展運(yùn)算符方式
/* 對(duì)象的拷貝 */
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
obj.a = 2
console.log(obj) //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj) //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
/* 數(shù)組的拷貝 */
let arr = [1, 2, 3];
let newArr = [...arr]; 方法三:concat和slice 淺拷貝數(shù)組
僅僅針對(duì)數(shù)組類型,都會(huì)返回一個(gè)新的數(shù)組對(duì)象。
concat 淺拷貝數(shù)組
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr); // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]
slice 淺拷貝數(shù)組
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]深拷貝
將一個(gè)對(duì)象從內(nèi)存中完整地拷貝出來(lái)一份給目標(biāo)對(duì)象,并從堆內(nèi)存中開辟一個(gè)全新的空間存放新對(duì)象,且新對(duì)象的修改并不會(huì)改變?cè)瓕?duì)象,二者實(shí)現(xiàn)真正的分離。

實(shí)現(xiàn)方法
方法一:乞丐版(JSON.stringify和JSON.parse)
let obj1 = { a:1, b:[1,2,3] }
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a = 2;
obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}缺陷:
- 拷貝的對(duì)象的值中如果有函數(shù)、undefined、symbol 這幾種類型,經(jīng)過(guò) JSON.stringify 序列化之后的字符串中這個(gè)鍵值對(duì)會(huì)消失
- 拷貝 Date 引用類型會(huì)變成字符串
- 無(wú)法拷貝不可枚舉的屬性
- 無(wú)法拷貝對(duì)象的原型鏈
- 拷貝 RegExp 引用類型會(huì)變成空對(duì)象
- 對(duì)象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的結(jié)果會(huì)變成 null
手寫遞歸實(shí)現(xiàn)
基礎(chǔ)版
let obj = {a:{b:1}};
function deepClone(obj){
let cloneObj = Object.prototype.toString.call(obj) === "[object Array]" ? [] : {};
for(let key in obj){
if(typeof obj[key]=== "object" && obj !== null){
cloneObj[key] = deepClone(obj[key])
}else{
cloneObj[key] = obj[key]
}
}
return cloneObj;
}
let obj2 = deepClone(obj);
obj2.a.b = 33
console.log("obj",obj) // {a:{b:1}}
console.log("obj2",obj2) // {a:{b:33}}缺陷:
- 不能復(fù)制不可枚舉的屬性以及 Symbol 類型
- 這種方法只是針對(duì)普通的引用類型的值做遞歸復(fù)制,而對(duì)于 Array、Date、RegExp、Error、Function 這樣的引用類型并不能正確地拷貝
- 對(duì)象的屬性里面成環(huán),即循環(huán)引用沒有解決
改進(jìn)版
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date)
return new Date(obj) // 日期對(duì)象直接返回一個(gè)新的日期對(duì)象
if (obj.constructor === RegExp)
return new RegExp(obj) //正則對(duì)象直接返回一個(gè)新的正則對(duì)象
//如果循環(huán)引用了就用 weakMap 來(lái)解決
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍歷傳入?yún)?shù)所有鍵的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
//繼承原型鏈
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}手寫深拷貝參考:
http://www.dbjr.com.cn/article/247540.htm
http://www.dbjr.com.cn/article/247544.htm
其他實(shí)現(xiàn)方法
lodash庫(kù)的_.cloneDeep方法,jQuery.extend()方法
FAQ:賦值和深淺拷貝的區(qū)別
注意:前提都是針對(duì)引用類型
賦值
當(dāng)我們把一個(gè)對(duì)象賦值給一個(gè)新的變量時(shí),賦的其實(shí)是該對(duì)象的在棧中的地址,而不是堆中的數(shù)據(jù)。也就是兩個(gè)對(duì)象指向的是同一個(gè)存儲(chǔ)空間,無(wú)論哪個(gè)對(duì)象發(fā)生改變,其實(shí)都是改變的存儲(chǔ)空間的內(nèi)容,因此,兩個(gè)對(duì)象是聯(lián)動(dòng)的。
let a = {name:'jimmy',b:{age:12}}
let b = a;
b.name = 'chimmy';
b.b.age = 21;
console.log(a) // {name:'chimmy',b:{age:21}}
console.log(b) // {name:'chimmy',b:{age:21}}淺拷貝
重新在堆中創(chuàng)建內(nèi)存,拷貝前后對(duì)象的基本數(shù)據(jù)類型互不影響,但拷貝前后對(duì)象的引用類型因共享同一塊內(nèi)存,會(huì)相互影響。
let a = {name:'jimmy',b:{age:12}}
let b = {...a};
b.name = 'chimmy';
b.b.age = 21;
console.log(a) // {name:'jimmy',b:{age:21}}
console.log(b) // {name:'chimmy',b:{age:21}}深拷貝
將一個(gè)對(duì)象從內(nèi)存中完整地拷貝出來(lái)一份給目標(biāo)對(duì)象,并從堆內(nèi)存中開辟一個(gè)全新的空間存放新對(duì)象,且新對(duì)象的修改并不會(huì)改變?cè)瓕?duì)象,二者實(shí)現(xiàn)真正的分離。
let a = {name:'jimmy',b:{age:12}}
let b = {...a};
b.name = 'chimmy';
b.b.age = 21;
console.log(a) // {name:'jimmy',b:{age:12}}
console.log(b) // {name:'chimmy',b:{age:21}}總結(jié)
到此這篇關(guān)于js深拷貝和淺拷貝的文章就介紹到這了,更多相關(guān)js深拷貝和淺拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
window.location.href和window.open窗口跳轉(zhuǎn)區(qū)別解析
這篇文章主要為大家介紹了window.location.href和window.open 跳轉(zhuǎn)區(qū)別解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
countUp.js實(shí)現(xiàn)數(shù)字動(dòng)態(tài)變化效果
這篇文章主要為大家詳細(xì)介紹了countUp.js實(shí)現(xiàn)數(shù)字動(dòng)態(tài)變化效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
webpack-dev-server搭建本地服務(wù)器的實(shí)現(xiàn)
當(dāng)我們使用webpack打包時(shí),發(fā)現(xiàn)每次更新了一點(diǎn)代碼,都需要重新打包,我們希望本地能搭建一個(gè)服務(wù)器,本文就介紹如何使用webpack-dev-server搭建本地服務(wù)器,感興趣的可以了解一下2021-07-07
移動(dòng)端日期插件Mobiscroll.js使用詳解
這篇文章主要為大家詳細(xì)介紹了移動(dòng)端日期插件Mobiscroll.js的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
關(guān)于JavaScript中var聲明變量作用域的推斷
這個(gè)問題其實(shí)之前困擾了我很久。如今終于想明白了,特來(lái)分享,如果有錯(cuò)誤的地方,請(qǐng)幫忙指正,我會(huì)隨時(shí)回來(lái)修正滴。2010-12-12
javascript使用appendChild追加節(jié)點(diǎn)實(shí)例
這篇文章主要介紹了javascript使用appendChild追加節(jié)點(diǎn)的方法,實(shí)例分析了appendChild()函數(shù)增加結(jié)點(diǎn)的使用技巧,需要的朋友可以參考下2015-01-01
Js 隨機(jī)數(shù)產(chǎn)生6位數(shù)字
Js隨機(jī)產(chǎn)生6為數(shù)字的代碼,需要的朋友可以參考下。2010-05-05

