對於這個問題,能夠考慮從深拷貝和淺拷貝的使用或者起源提及,也就是爲何會出現這個問題。javascript
首先了解一些javascript的基本知識。java
【1】javascript變量包含兩種不一樣數據類型的值:基本類型和引用類型。es6
①基本類型值指的是簡單的數據段,包括es6裏面新增的一共是有6種,具體以下:數組
String、Number、Boolean、Null、Undefined、Symbol函數
②引用類型值指那些可能由多個值構成的對象,具體以下:測試
Object(Object、Array、Function)ui
在將一個值賦給變量時,解析器必須肯定這個值是基本類型值仍是引用類型值。基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值。spa
引用類型的值是保存在內存中的對象。與其餘語言不一樣,JavaScript 不容許直接訪問內存中的位置,也就是說不能直接操做對象的內存空間。 在操做對象時, 其實是在操做對象的引用而不是實際的對象。3d
【2】javascript的變量的存儲方式--棧(stack)和堆(heap)code
棧:自動分配內存空間,系統自動釋放,裏面存放的是基本類型的值和引用類型的地址
堆:動態分配的內存,大小不定,也不會自動釋放。裏面存放引用類型的值。
【3】javascript值傳遞與址傳遞
基本類型與引用類型最大的區別實際就是傳值與傳址的區別
值傳遞:基本類型採用的是值傳遞。
let a = 10; // 定義一個變量a並賦值爲10
let b = a; // 將a的值10賦值給b (a、b都是基本類型,值傳遞)
b++; // b自加
console.log(a, b) // 10, 11複製代碼
址傳遞:引用類型則是地址傳遞,將存放在棧內存中的地址賦值給接收的變量。
let a = ['a', 'b', 'c']; // 定義一個數組a並賦值
let b = a; // 數組是引用類型,採用地址傳遞,將a的地址賦值給b
b.push('d'); // 給b數組增長一個'd'元素
console.log(a) // ['a', 'b', 'c', 'd']
console.log(b) // ['a', 'b', 'c', 'd']複製代碼
分析:因爲a和b都是引用類型,採用的是址傳遞,即a將地址傳遞給b,那麼a和b必然指向同一個地址(引用類型的地址存放在棧內存中),而這個地址都指向了堆內存中引用類型的值。當b改變了這個值的同時,由於a的地址也指向了這個值,故a的值也跟着變化。
舉例:就比如是a租了一間房,將房間的地址給了b,b經過地址找到了房間,那麼b對房間作的任何改變(添加了一些綠色植物)對a來講確定一樣是可見的。那麼如何解決上面出現的問題,就是使用淺拷貝或者深拷貝了。 JS的基本類型不存在淺拷貝仍是深拷貝的問題,主要是針對於引用類型
【4】淺拷貝和深拷貝區別總結
字面意思:
淺拷貝---拷貝的級別淺。
深拷貝---拷貝級別更深。
具體:
淺拷貝---淺拷貝是指複製對象的時候,只對第一層鍵值對進行獨立的複製,若是對象內還有對象,則只能複製嵌套對象的地址
深拷貝---深拷貝是指複製對象的時候徹底的拷貝一份對象,即便嵌套了對象,二者也相互分離,修改一個對象的屬性,也不會影響另外一個。其實只要遞歸下去,把那些屬性的值仍然是對象的再次進入對象內部一 一進行復制便可。
淺拷貝解決就是先設置一個新的對象obj2,經過遍歷的方式將obj1對象的值一 一賦值給obj2對象。
// 數組的淺拷貝
let arr1 = [1, 2, 3]
let arr2 = []
for (let i in arr1) {
arr2[i] = arr1[i]
}
arr2.push(4)
console.log(arr1) // [1, 2, 3]
console.log(arr2) // [1, 2, 3, 4]
// 對象的淺拷貝
let obj1 = {
a: '1',
b: '2',
c: '3'
}
let obj2 = {}
for (let i in obj1) {
obj2[i] = obj1[i]
}
obj2.d = '4'
console.log(obj1) // {a: "1", b: "2", c: "3"}
console.log(obj2) // {a: "1", b: "2", c: "3", d: "4"}
// 淺拷貝函數封裝
function shallowCopy(obj1, obj2) {
for(var key in obj1) {
obj2[key] = obj1[key]
}
}複製代碼
但上面代碼只能實現一層的拷貝,沒法進行深層次的拷貝,封裝函數再次經過對象數組嵌套測試以下:
// 淺拷貝函數封裝
function shallowCopy(obj1, obj2) {
for(var key in obj1) {
obj2[key] = obj1[key]
}
}
// 對象的淺拷貝
let obj1 = {
a: '1',
b: '2',
c: {
name: 'Demi'
}
}
let obj2 = {}
shallowCopy(obj1, obj2) //將obj1的數據拷貝到obj2
obj2.c.name = 'dingFY'
console.log(obj1) // {a: "1", b: "2", c: {name: 'dingFY'}}
console.log(obj2) // {a: "1", b: "2", c: {name: 'dingFY'}}複製代碼
結果證實,若是對象內還有對象,則只能複製嵌套對象的地址,沒法進行深層次的拷貝,當改變obj2嵌套對象c的值後,obj1嵌套對象c的值也跟着變了
這個時候咱們可使用深拷貝來完成,所謂深拷貝,就是可以實現真正意義上的數組和對象的拷貝,咱們經過遞歸調用淺拷貝的方式實現。
// 深拷貝函數封裝
function deepCopy(obj) {
// 根據obj的類型判斷是新建一個數組仍是對象
let newObj = Array.isArray(obj)? [] : {};
// 判斷傳入的obj存在,且類型爲對象
if (obj && typeof obj === 'object') {
for(key in obj) {
// 若是obj的子元素是對象,則進行遞歸操做
if(obj[key] && typeof obj[key] ==='object') {
newObj[key] = deepCopy(obj[key])
} else {
// // 若是obj的子元素不是對象,則直接賦值
newObj[key] = obj[key]
}
}
}
return newObj // 返回新對象
}
// 對象的深拷貝
let obj1 = {
a: '1',
b: '2',
c: {
name: 'Demi'
}
}
let obj2 = deepCopy(obj1) //將obj1的數據拷貝到obj2
obj2.c.name = 'dingFY'
console.log(obj1) // {a: "1", b: "2", c: {name: 'Demi'}}
console.log(obj2) // {a: "1", b: "2", c: {name: 'dingFY'}}複製代碼
結果證實上面的代碼能夠實現深層次的克隆。
若是是數組,咱們能夠利用數組的一些方法好比:slice、concat 返回一個新數組的特性來實現拷貝。
【1】Array.concat()
let arr = ['one', 'two', 'three'];
let newArr = arr.concat();
newArr.push('four')
console.log(arr) // ["one", "two", "three"]
console.log(newArr) // ["one", "two", "three", "four"]複製代碼
【2】Array.slice()
let arr = ['one', 'two', 'three'];
let newArr = arr.slice();
newArr.push('four')
console.log(arr) // ["one", "two", "three"]
console.log(newArr) // ["one", "two", "three", "four"]複製代碼
這裏介紹一個技巧,不只適用於數組還適用於對象!不過存在一個問題,就是不能拷貝函數
let arr = {
a: 'one',
b: 'two',
c: {
name: 'Demi'
}
};
let newArr = JSON.parse( JSON.stringify(arr) );
newArr.c.name = 'dingFY'
console.log(arr); // {a: "one", b: "two", c: {name: 'Demi'}}
console.log(newArr); // {a: "one", b: "two", c: {name: 'dingFY'}}
// 測試函數可否複製
let arr = {
a: 'one',
b: ()=>{
console.log('test')
}
};
let newArr = JSON.parse( JSON.stringify(arr) );
console.log(arr); // {a: "one", b: ()=>{console.log('test')}}
console.log(newArr); // {a: "one"} // 函數沒有複製成功複製代碼
Object.assign()方法能夠把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,而後返回目標對象。可是 Object.assign() 進行的是淺拷貝
let arr = {
a: 'one',
b: 'two',
c: 'three'
};
let newArr = Object.assign({}, arr)
newArr.d = 'four'
console.log(arr); // {a: "one", b: "two", c: "three"}
console.log(newArr); // {a: "one", b: "two", c: "three", d: "four"}複製代碼
原理:遍歷對象,而後把屬性和屬性值都放在一個新的對象
let shallowCopy = function (obj) {
// 只拷貝對象
if (typeof obj !== 'object') return;
// 根據obj的類型判斷是新建一個數組仍是對象
let newObj = obj instanceof Array ? [] : {};
// 遍歷obj,而且判斷是obj的屬性才拷貝
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}複製代碼
原理:咱們在拷貝的時候判斷一下屬性值的類型,若是是對象,咱們遞歸調用深拷貝函數就行了
let deepCopy = function (obj) {
// 只拷貝對象
if (typeof obj !== 'object') return;
// 根據obj的類型判斷是新建一個數組仍是對象
let newObj = obj instanceof Array ? [] : {};
// 遍歷obj,而且判斷是obj的屬性才拷貝
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 若是obj的子屬性是對象,則進行遞歸操做,不然直接賦值
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}複製代碼