深入剖析javaScript中的深拷貝和淺拷貝

深入剖析javaScript中的深拷貝和淺拷貝

在面試時經常會碰到面試官問:什麼是深拷貝和淺拷貝,請舉例說明?如何區分深拷貝與淺拷貝,簡單來說,假設B複製了A,當修改A時,看B是否會發生變化,如果B也跟著變了,說明這是淺拷貝,如果B沒變,那就是深拷貝;我們先看兩個簡單的案例:

//案例1

var a1 = 1, a2= a1;

console.log(a1) //1

console.log(a2) //1

a2 = 2; //修改 a2

console.log(a1) //1

console.log(a2) //2

//案例2

var o1 = {x: 1, y: 2}, o2 = o1;

console.log(o1) //{x: 1, y: 2}

console.log(o2) //{x: 1, y: 2}

o2.x = 2; //修改o2.x

console.log(o1) //{x: 2, y: 2}

console.log(o2) //{x: 2, y: 2}

按照常規思維,o1應該和a1一樣,不會因為另外一個值的改變而改變,而這裡的o1 卻隨著o2的改變而改變了。同樣是變量,為什麼表現不一樣呢?為了更好的理解js的深淺拷貝,我們先來理解一些js基本的概念 —— 目前JavaScript有五種基本數據類型(也就是簡單數據類型),它們分別是:Undefined,Null,Boolean,Number和String。還含有一種複雜的數據類型(也叫引用類型),就是對象,引用類型有:Object、Array、Function(之所以說“目前”,因為之後也可能會有新的類型出來。)

一、基本類型和引用類型

ECMAScript變量可能包含兩種不同數據類型的值:基本類型值和引用類型值。基本類型值指的是那些保存在棧內存中的簡單數據段,即這種值完全保存在內存中的一個位置。而引用類型值是指那些保存堆內存中的對象,意思是變量中保存的實際上只是一個指針,這個指針指向內存中的另一個位置,該位置保存對象。

打個比方,基本類型和引用類型在賦值上的區別可以按“雙胞胎”和“影子”來理解:基本類型賦值等於一位媽媽生的雙胞胎,二者互不相關,各自有各自的特性;而引用類型賦值相當於自己跟影子,你在變化時影子會隨著變化;

上面清晰明瞭的介紹了基本類型和引用類型的定義和區別。

再回到前面的案例,案例1中的值為基本類型,案例2中的值為引用類型。案例2中的賦值就是典型的淺拷貝,並且深拷貝與淺拷貝的概念只存在於引用類型。

二、深拷貝與淺拷貝

既然已經知道了深拷貝與淺拷貝的來由,那麼該如何實現深拷貝?我們先分別看看Array和Object自有方法是否支持:

1、Array

對於數組我們可以使用slice() 和 concat() 方法來解決上面的問題

1) slice

var arr1 = ['a', 'b'], arr2 = arr1.slice();

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["a", "b"]

arr2[0] = 'c'; //修改arr2

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["c", "b"]

此時,arr2的修改並沒有影響到arr1,看來深拷貝的實現並沒有那麼難嘛。

2) concat

var arr1 = ['a', 'b'], arr2 = arr1. concat ();

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["a", "b"]

arr2[0] = 'c'; //修改arr2

console.log(arr1); // ["a", "b"]

console.log(arr2); // ["c", "b"]

我們把arr1改成二維數組再來看看:

var arr1 = ['a', 'b', ['c', 'd']], arr2 = arr1.concat();

arr2[2][1] = 100;

console.log(arr1); //['a', 'b', ['c', 100]]

console.log(arr2); //['a', 'b', ['c', 100]]

咦,arr2又改變了arr1,看來slice()/concat()只能實現一維數組的深拷貝

除了上面兩種方法外,我們還可以借用JQ的extend方法。

$.extend( [deep ], target, object1 [, objectN ] )

deep表示是否深拷貝,為true為深拷貝,為false,則為淺拷貝

target Object類型 目標對象,其他對象的成員屬性將被附加到該對象上。

object1 objectN可選。 Object類型 第一個以及第N個被合併的對象。

var a=[0,1,[2,3],4],

b=$.extend(true,[],a);

a[0]=1;

a[2][0]=1; //

console.log(a); //[1,1,[1,3],4]

console.log(b); //[0,1,[2,3],4]

不過這種方法需要依賴JQ庫。

2、Object

1) 利用對象的深拷貝實現原理

定義一個新的對象,遍歷源對象的屬性 並 賦給新對象的屬性

var obj = {

name:'sonia',

age: 18

}

var obj2 = new Object();

obj2.name = obj.name;

obj2.age = obj.age

obj.name = 'alice';

console.log(obj); //Object {name: "'alice'", age: 18}

console.log(obj2); //Object {name: "'sonia'", age: 18}

理解了以上的基本思想,我們就可以封裝一個方法 deepCopy來實現對象的深拷貝,代碼如下

var obj = {

name: 'sonia',

age: 18

}

var deepCopy = function (source) {

var result = {};

for(var key in source) {

if(typeof source[key] === 'object') {

result[key] = deepCopy(source[key])

} else {

result[key] = source[key]

}

}

return result;

}

var objCopy = deepCopy(obj)

obj.name = 'aaa';

console.log(obj);//Object {name: "aaa", age: 18}

console.log(objCopy);//Object {name: "sonia", age: 18}

2) JSON.parse(JSON.stringify(obj))

var obj = {

name: 'sonia',

age: 18

}

var obj2 = JSON.parse(JSON.stringify(obj));

obj.name = 'alice';

console.log(obj) // {name: "alice", age: 18}

console.log(obj2) // {name: "sonia", age: 18}

瞭解拷貝也不僅僅是為了應付面試,在實際開發中也是非常有用的。

希望能給大家帶來收穫,如果覺得有收穫歡迎轉發+關注+評論666。你們的支持是我更新最大的動力!


分享到:


相關文章: