關(guān)于淺拷貝和深拷貝的介紹
淺拷貝
淺拷貝只復(fù)制指向某個(gè)對(duì)象的指針泽台,而不復(fù)制對(duì)象本身,新舊對(duì)象還是共享同一塊內(nèi)存琅翻。
數(shù)據(jù)分為基本數(shù)據(jù)類型(String, Number, Boolean, Null, Undefined隐锭,Symbol)和對(duì)象數(shù)據(jù)類型。
- 基本數(shù)據(jù)類型的特點(diǎn):直接存儲(chǔ)在棧(stack)中的數(shù)據(jù)
- 引用數(shù)據(jù)類型的特點(diǎn):存儲(chǔ)的是該對(duì)象在棧中引用论悴,真實(shí)的數(shù)據(jù)存放在堆內(nèi)存里
引用數(shù)據(jù)類型在棧中存儲(chǔ)了指針掖棉,該指針指向堆中該實(shí)體的起始地址席爽。當(dāng)解釋器尋找引用值時(shí),會(huì)首先檢索其在棧中的地址啊片,取得地址后從堆中獲得實(shí)體只锻。
淺拷貝只復(fù)制指向某個(gè)對(duì)象的指針,而不復(fù)制對(duì)象本身紫谷,新舊對(duì)象還是共享同一塊內(nèi)存齐饮。但深拷貝會(huì)另外創(chuàng)造一個(gè)一模一樣的對(duì)象,新對(duì)象跟原對(duì)象不共享內(nèi)存笤昨,修改新對(duì)象不會(huì)改到原對(duì)象祖驱。
賦值和淺拷貝的區(qū)別
當(dāng)我們把一個(gè)對(duì)象賦值給一個(gè)新的變量時(shí)兵扬,賦的其實(shí)是該對(duì)象的在棧中的地址啡捶,而不是堆中的數(shù)據(jù)。也就是兩個(gè)對(duì)象指向的是同一個(gè)存儲(chǔ)空間拇舀,無論哪個(gè)對(duì)象發(fā)生改變崇裁,其實(shí)都是改變的存儲(chǔ)空間的內(nèi)容匕坯,因此,兩個(gè)對(duì)象是聯(lián)動(dòng)的拔稳。
淺拷貝是按位拷貝對(duì)象葛峻,它會(huì)創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝巴比。如果屬性是基本類型术奖,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型)轻绞,拷貝的就是內(nèi)存地址 采记,因此如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象政勃。即默認(rèn)拷貝構(gòu)造函數(shù)只是對(duì)對(duì)象進(jìn)行淺拷貝復(fù)制(逐個(gè)成員依次拷貝)唧龄,即只復(fù)制對(duì)象空間而不復(fù)制資源。
一句話概括就是稼病,賦值的話选侨,無論哪個(gè)對(duì)象發(fā)生變化,都會(huì)相互影響然走,因?yàn)橘x值是對(duì)對(duì)象在棧中的地址的賦值援制。而淺拷貝中的屬性值發(fā)生變化,不會(huì)影響芍瑞,因?yàn)槭前次豢截惓柯兀瑢?duì)于基本類型的話,拷貝的是基本類型的值。
淺拷貝的實(shí)現(xiàn)方式
-
objext.assign()
Object.assign() 方法可以把任意多個(gè)的源對(duì)象自身的可枚舉屬性拷貝給目標(biāo)對(duì)象洪己,然后返回目標(biāo)對(duì)象妥凳。但是 Object.assign()進(jìn)行的是淺拷貝,拷貝的是對(duì)象的屬性的引用答捕,而不是對(duì)象本身逝钥。
var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade
注意:當(dāng)object只有一層的時(shí)候,是深拷貝
let obj = {
username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}
2.Array.prototype.concat()
let arr = [1, 3, {
username: 'kobe'
}];
let arr2=arr.concat();
arr2[2].username = 'wade';
console.log(arr);
3.Array.prototype.slice()
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);
關(guān)于Array的slice和concat方法的補(bǔ)充說明:Array的slice和concat方法不修改原數(shù)組拱镐,只會(huì)返回一個(gè)淺復(fù)制了原數(shù)組中的元素的一個(gè)新數(shù)組艘款。
原數(shù)組的元素會(huì)按照下述規(guī)則拷貝:
如果該元素是個(gè)對(duì)象引用(不是實(shí)際的對(duì)象),slice 會(huì)拷貝這個(gè)對(duì)象引用到新的數(shù)組里沃琅。兩個(gè)對(duì)象引用都引用了同一個(gè)對(duì)象哗咆。如果被引用的對(duì)象發(fā)生改變,則新的和原來的數(shù)組中的這個(gè)元素也會(huì)發(fā)生改變益眉。
對(duì)于字符串晌柬、數(shù)字及布爾值來說(不是 String、Number 或者 Boolean 對(duì)象)郭脂,slice 會(huì)拷貝這些值到新的數(shù)組里年碘。在別的數(shù)組里修改這些字符串或數(shù)字或是布爾值,將不會(huì)影響另一個(gè)數(shù)組朱庆。
深拷貝
- JSON.parse(JSON.stringify())
let arr = [1, 3, {
username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
原理: 用JSON.stringify將對(duì)象轉(zhuǎn)成JSON字符串盛泡,再用JSON.parse()把字符串解析成對(duì)象,一去一來娱颊,新的對(duì)象產(chǎn)生了,而且對(duì)象會(huì)開辟新的棧凯砍,實(shí)現(xiàn)深拷貝箱硕。
這種方法雖然可以實(shí)現(xiàn)數(shù)組或?qū)ο笊羁截?但不能處理函數(shù)
let arr = [1, 3, {
username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
這是因?yàn)镴SON.stringify() 方法是將一個(gè)JavaScript值(對(duì)象或者數(shù)組)轉(zhuǎn)換為一個(gè) JSON字符串,不能接受函數(shù)
-
手寫遞歸方法
遞歸方法實(shí)現(xiàn)深度克隆原理:遍歷對(duì)象悟衩、數(shù)組直到里邊都是基本數(shù)據(jù)類型剧罩,然后再去復(fù)制,就是深度拷貝
//定義檢測(cè)數(shù)據(jù)類型的功能函數(shù)
function checkedType(target) {
//https://www.cnblogs.com/lmsblogs/p/11280821.html
// 在JavaScript里使用typeof判斷數(shù)據(jù)類型座泳,只能區(qū)分基本類型惠昔,即:number、string挑势、undefined镇防、boolean、object潮饱。
// 對(duì)于null来氧、array、function、object來說啦扬,使用typeof都會(huì)統(tǒng)一返回object字符串中狂。
// 要想?yún)^(qū)分對(duì)象、數(shù)組扑毡、函數(shù)胃榕、單純使用typeof是不行的。在JS中瞄摊,可以通過Object.prototype.toString方法勤晚,判斷某個(gè)對(duì)象之屬于哪種內(nèi)置類型。
// 分為null泉褐、string赐写、boolean、number膜赃、undefined挺邀、array、function跳座、object端铛、date、math疲眷。
// Object.prototype.toString.call(arr); // "[object Array]"
return Object.prototype.toString.call(target).slice(8, -1)
}
function clone(target) {
//判斷拷貝的數(shù)據(jù)類型
//初始化變量result 成為最終克隆的數(shù)據(jù)
let result,
targetType = checkedType(target)
if (targetType === 'Object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
//遍歷目標(biāo)數(shù)據(jù)
for (let i in target) {
//獲取遍歷數(shù)據(jù)結(jié)構(gòu)的每一項(xiàng)值禾蚕。
let value = target[i]
//判斷目標(biāo)結(jié)構(gòu)里的每一值是否存在對(duì)象/數(shù)組
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
//對(duì)象/數(shù)組里嵌套了對(duì)象/數(shù)組
//繼續(xù)遍歷獲取到value值
result[i] = clone(value)
} else {
//獲取到value值是基本的數(shù)據(jù)類型或者是函數(shù)。
result[i] = value
}
}
return result
}
3.函數(shù)庫lodash
該函數(shù)庫也有提供_.cloneDeep用來做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false