JS 對象的深拷貝和淺拷貝

現象

我們先來看一個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。

放水完畢!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章