現象
我們先來看一個demo
// 我們先申明一個變量str1,
// 然後把變量str1負值(拷貝)給變量str2
// 最後對變量str2進行修改操作
var str1 = 'shen'
var str2 = str1
str2 += 'zhiyong'
console.log('str1:', str1) //shen
console.log('str2:', str2) //shenzhiyong
我們申明一個對象並對它進行相同的操作
var obj1 = {
name: 'shen'
}
var obj2 = obj1
obj2.name = 'shenzhiyong'
//期望輸出
// obj1: {name: "shen"}
// obj2: {name: "shenzhiyong"}
console.log('obj1:', obj1) // obj1: {name: "shenzhiyong"}
console.log('obj2:', obj2) // obj2: {name: "shenzhiyong"}
我們不難發現結果並不是我們預期的那樣, 這是爲什麼呢?如果將obj1改成數組對象進行操作,亦是同樣的結果。
原因:由於String類型屬於基本數據類型,Object(Array)屬於引用數據類型。當我們申明一個基本類型並對它進行賦值的時候,計算機會將值保存在棧內存中。而當我們申明一個引用數據類型並對它進行賦值的時候,計算機會將值保存在堆內存中,引用類型變量其實就是一個指針指向堆內存中。如果複製兩相同的引用類型變量,其實它們最終指向同一個對象或者說堆內存空間。
關於JavaScript的數據類型 我們先埋下一個坑!
一、淺拷貝
對象和數組的淺拷貝代碼如下:
var obj1 = {
name: 'shen'
}
var obj2 = obj1
obj2.name = 'shenzhiyong'
console.log('obj1:', obj1) // obj1: {name: "shenzhiyong"}
console.log('obj2:', obj2) // obj2: {name: "shenzhiyong"}
var arr1 = [1,2,3]
var arr2 = arr1
arr2.push(4)
console.log('arr1:', arr1) // arr1: [1,2,3,4]
console.log('arr2:', arr2) // arr2: [1,2,3,4]
淺拷貝的意思就是隻複製引用,沒有複製真正的值。有時候我們只是想保留對象的數據,單純想改變obj2和arr2的值,但是原對象的數據也發生了改變。很多時候這種情況都不是我們想要的。爲了解決這個問題: 深拷貝它來了!
二、深拷貝
JSON方法
var obj1 = {
name: 'shen'
}
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = 'shenzhiyong'
console.log('obj1:', obj1) // obj1: {name: "shen"}
console.log('obj2:', obj2) // obj2: {name: "shenzhiyong"}
優點:簡單明瞭,方便記憶
缺點:看下面代碼。當對象裏面出現函數的時候就不適用了。
var obj1 = {
name: 'shen',
show: function (argument) {
console.log(1)
}
}
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log('obj1:', obj1) // obj1: {name: "shen", show: ƒ}
console.log('obj2:', obj2) // obj2: {name: "shen"}
手寫遞歸方法
function deepCopy(obj) {
var newobj = obj.constructor === Array ? [] : {};
if (typeof obj !== 'object') {
return obj;
} else {
for (var i in obj) {
if (typeof obj[i] === 'object'){ //判斷對象的這條屬性是否爲對象
newobj[i] = deepCopy(obj[i]); //若是對象進行嵌套調用
}else{
newobj[i] = obj[i];
}
}
}
return newobj; //返回深度克隆後的對象
}
var obj1 = {
name: 'shen',
show: function (argument) {
console.log(1)
}
}
var obj2 = deepCopy(obj1)
console.log('obj1:', obj1) // obj1: {name: "shen", show: ƒ}
console.log('obj2:', obj2) // obj2: {name: "shen"}
優點:能夠實現對象和數組的深拷貝
缺點:如果拷貝的對象嵌套過深的話,會對性能有一定的消耗
第三方庫 jQuery.extend 和 lodash
$.extend( true, object1, object2 ); // 深度拷貝
$.extend( object1, object2 ); // 淺拷貝
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]); // => false
大佬寫的東西,我只能說:逃
三、重點來了:具有侷限性的深拷貝(有些面試官就喜歡問這個地方的東西)
當對象或者數組內部的都是基本數據類型的話,以下的方式可以實現深拷貝。但是如果出現了引用類型嵌套引用類型的話。以下方法將不可用。
看下面代碼:
var obj = {
name: 'shen'
}
var obj2 = {
innner: {
name: 'shen'
}
}
以下方法僅支持obj此類對象的深度拷貝不支持obj2此類對象
es6解析結構 「…」
var obj1 = {
name: 'shen',
show: function (argument) {
console.log(1)
}
}
var obj2 = { ...obj1 }
obj2.name = 'shenzhiyong'
console.log('obj1:', obj1) // obj {name: "szy", show: ƒ}
console.log('obj2:', obj2) // obj2 {name: "shenzhiyong", show: ƒ}
Object.assign()
var obj1 = {
name: 'shen',
show: function (argument) {
console.log(1)
}
}
var obj2 = Object.assign(obj1)
obj2.name = 'shenzhiyong'
console.log('obj1:', obj1) // obj {name: "szy", show: ƒ}
console.log('obj2:', obj2) // obj2 {name: "shenzhiyong", show: ƒ}
數組中的slice() & concat()
var arr1 = [1,2,3]
var arr2 = arr1.slice() // 方法一
// var arr2 = arr1.concat() //方法二
arr2.push(4)
console.log('arr1:', arr1) // arr1: [1, 2, 3]
console.log('arr2:', arr2) // arr1: [1, 2, 3, 4]
總結:要想實現真正意義的深拷貝,個人覺得還是遞歸的方法比較靠譜。其實看第三方的庫也是採用這樣的做法。在實際的生產當中,我使用的是lodash。
放水完畢!