一透绩、如何區(qū)分深拷貝和淺拷貝?
假設B復制了A壁熄,當修改A時帚豪,看B是否發(fā)生變化,如果B也跟著變了草丧,說明這是淺拷貝狸臣,拿人手短;
如果B沒變方仿,那就是深拷貝固棚,自食其力。
二仙蚜、淺拷貝
let a = [0,1,2,3,4];
b = a;
console.log(a===b); // true
a[0] = 1;
console.log(a, b); // [1,1,2,3,4] [1,1,2,3,4]
可以發(fā)現(xiàn),b復制了a厂汗,修改數(shù)組a委粉,數(shù)組b也跟著改變了。
這里娶桦,就要引入js的數(shù)據(jù)類型了贾节。JS數(shù)據(jù)類型包括基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,基本數(shù)據(jù)類型包括string衷畦、number栗涂、Boolean、null祈争、undefined斤程;引用數(shù)據(jù)類型包括Object、arr菩混、函數(shù)等忿墅。
a. 對于基本數(shù)據(jù)類型扁藕,其存儲方式是按值存儲的。如下:
let a = 1 名值存儲在棧內(nèi)存中疚脐。
當b=a復制時亿柑,棧內(nèi)存會開辟一個新的內(nèi)存,如下:
所以當你此時修改a=2棍弄,對b并不會造成影響望薄,因為此時的b已經(jīng)自食其力了,不受a影響了呼畸。但是式矫,雖然b不受a影響了,這也算不上深拷貝役耕,因為深拷貝本身是針對較為復雜的object類型數(shù)據(jù)采转。
b. 對于引用數(shù)據(jù)類型,其名存在棧內(nèi)存中瞬痘,值存在堆內(nèi)存中故慈,但是棧內(nèi)存會提供一個引用的地址指向堆內(nèi)存中的值,如下:
當b=a進行復制時框全,其實復制的是a的引用地址察绷,而并非堆里面的值。a和b指向的是同一個地址津辩。
當a[0] = 1 對數(shù)組a進行修改時拆撼,由于a和b指向同一個地址,所以b自然也受到影響喘沿,這就是所謂的淺拷貝了闸度。
要是在堆內(nèi)存中也開辟一個新的內(nèi)存專門為b存放值,這樣就達到深拷貝的效果了蚜印。
淺拷貝在我看來是對“一層”簡單類型的拷貝莺禁,也就是淺拷貝只對一層有效,是指向不同內(nèi)存地址窄赋。
三哟冬、淺拷貝實現(xiàn)方法
1、直接遍歷
var array = [1,2,3,4];
function copy (array) {
let newArray = [];
for (let item of array) {
newArray.push(item);
}
return newArray;
}
var copyArray = copy(array);
copyArray[0] = 100;
console.log(array); // [1,2,3,4]
console.log(copyArray); // [100,2,3,4]
或者
function shallowCopy (obj) {
var copyObj = {};
for (let i in obj) {
copyObj[i] = obj[i]
}
return copyObj
}
var x = {
a: 1,
b: {f: {g: 1}},
c: [1,2,3]
};
var y = shallowCopy(x);
console.log(y); // 與x一樣
console.log(x); //
y.a = 2;
console.log(y); // y.a = 2
console.log(x); // x.a = 1 不受影響忆绰,因為是a基本類型
y.b.f.g = 3;
console.log(y); // y.b.f.g = 3
console.log(x); // x.b.f.g = 3 受到影響浩峡,因為b是引用類型
y.c.push(4);
console.log(y); // y.c = [1,2,3,4]
console.log(x); // x.c = [1,2,3,4] 受到影響,因為c是引用類型
2错敢、slice()
var array = [1,2,3,4];
var copyArray = array.slice();
copyArray[0] = 100;
console.log(array); // [1,2,3,4]
console.log(copyArray); // [100,2,3,4]
slice() 方法返回一個從已有的數(shù)組中截取一部分元素片段組成的新數(shù)組(不改變原來的數(shù)組)翰灾,當slice不帶任何參數(shù)時,默認返回一個長度和原數(shù)組相同的數(shù)組。
3预侯、concat()
var array = [1,2,3,4];
var copyArray = array.concat();
copyArray[0] = 100;
console.log(array); // [1,2,3,4]
console.log(copyArray); // [100,2,3,4]
concat()方法用于連接兩個或多個數(shù)組(該方法不會改變原有的數(shù)組致开,而僅僅會返回被連接數(shù)組的一個版本)。var copyArray = array.concat();實際上相當于var copyArray = array.concat([]); 就是將返回數(shù)組和一個空數(shù)組合并萎馅。
4双戳、Object.assign()
Object.assign(target, ...sources) // target是目標對象,sources是源對象糜芳,可以是多個飒货,修改返回的是目標對象target。
如果目標對象中的屬性具有相同的屬性值峭竣,則屬性將被源對象中的屬性覆蓋塘辅。
const obj1 = {a: {b: 1}}
const obj2 = Object.assign({}, obj1); // {a: {b: 1}}
obj1.a.b = 2;
console.log(obj2.a.b); // 2
Object.assign方法肯定是不會拷貝原型鏈上的屬性,所以模擬實現(xiàn)時需要用hasOwnProperty(..)判斷處理下皆撩,但是直接使用myObject.hasOwnProperty(..)是有問題的扣墩,因為有的對象可能沒有連接到Object.prototype上(通過Object.create(null)來創(chuàng)建),這種情況下扛吞,使用myObject.hasOwnProperty(..)就會失敗呻惕。這個時候,使用call()來解決滥比。
var myObject = Object.create(null);
myObject.b = 2;
Object.prototype.hasOwnProperty.call(myObject, 'b');
但是亚脆,淺拷貝的意思是對于一級數(shù)組元素是基本類型變量的簡單數(shù)組,上面這三種拷貝方式都能成功盲泛,但對于第一級元素是對象或者數(shù)組等引用類型變量的數(shù)組濒持,上面的三種方法就失效了,那就需要深拷貝了寺滚。
四柑营、深拷貝
1、簡單暴力用JSON對象的parse和stringfy
JSON.parse()方法是取一個JSON字符串將其轉(zhuǎn)換為JavaScript對象玛迄。
JSON.stringify()方法取一個JSON對象由境,并將其轉(zhuǎn)換為JSON字符串。
因為怕影響原對象蓖议,所以深拷貝一份對象出來。
function deepClone (obj) {
let objClone = JSON.parse(JSON.stringify(obj));
return objClone;
}
let a = [0,1,[2,3],4],
b = deepClone(a);
a[0] = 1;
a[2][0] = 1;
console.log(b); // [0,1,[2,3],4]
使用JSON.stringify()以及JSON.parse()它是不可以拷貝 undefined 讥蟆, function勒虾, RegExp 等等類型的。不能解決循環(huán)引用的對象瘸彤。
2修然、封裝一個深拷貝的函數(shù)
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {}; // 或者
if (obj && typeof obj === 'object') {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// 判斷obj子元素是否也是對象,如果是,遞歸復制
if (obj[key] && typeof obj[key] === 'object') {
objClone[key] = deepClone(obj[key]);
} else {
// 如果不是愕宋,簡單復制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a = [1,2,3,4],
b = deepClone(a);
a[0] = 2;
console.log(a); // [2,2,3,4]
console.log(b); // [1,2,3,4]
但是玻靡,上面的代碼有很多問題,如下:
1中贝、沒有對參數(shù)做校驗
函數(shù)需要校驗參數(shù)囤捻,如果不是對象的話直接返回
function deepClone(obj) {
if (!isObject(obj)) return obj;
// xxx
}
2、判斷是否是對象邏輯不夠嚴謹
判斷對象的嚴謹方法
Object.prototype.toString.call(obj) === '[object Object]';
3邻寿、沒有考慮數(shù)組的兼容
更推薦lodash的深拷貝函數(shù)[https://lodash.com/docs/4.17.15#cloneDeep]
參考文章https://blog.csdn.net/dingyu002/article/details/117994305
----完整校驗的代碼如下:
function deepClone (obj) {
// 定義一個變量
let result;
// 如果當前需要深拷貝的是一個對象的話
if (obj && typeof obj === 'object') {
// 如果是一個數(shù)組的話
if (Array.isArray(obj)) {
result = []; // 將result賦值為一個數(shù)組蝎土,并且執(zhí)行遍歷
for (let i in obj) {
// 遞歸克隆數(shù)組中的每一項
result.push(deepClone(obj[i]));
}
// 如果當前值是null的話,直接賦值為null
} else if (obj === null) {
result = null;
// 如果當前的值是一個RegExp對象的話绣否,直接賦值
} else if (obj.constructor === RegExp) {
result = obj;
// 否則就是普通對象誊涯,直接for in 循環(huán),遞歸賦值對象的所有值
} else {
result = {};
for (let i in obj) {
result[i] = deepClone(obj[i]);
}
}
// 如果不是對象的話蒜撮,就是基本數(shù)據(jù)類型暴构,那么直接賦值
} else {
result = obj;
}
// 返回最終結(jié)果
return result;
}
使用in和hasOwnProperty方法
- in 操作符會檢查屬性是否在對象及其原型鏈中。
- hasownProperty()只會檢查是否在myObject對象中段磨,不會檢查原型鏈取逾。
這里再次強調(diào)下,深拷貝是拷貝對象各個層級的屬性薇溃。