關於Javascript中的Shallow Copy(淺拷貝)及Deep Copy(深拷貝)

關於Javascript中的Shallow Copy(淺拷貝)及Deep Copy(深拷貝)

關於Javascript中的Shallow Copy(淺拷貝)及Deep Copy(深拷貝), 前幾天在群組看到有人在討論Shallow copy 及 Deep copy,上網查了些資料發現收穫良多,對JS又有更進一步的認識了~這邊做個筆記紀錄一下。

型別介紹

在Javascript裡面,有分為原始型別(Primitive Type)及物件(Non-Primitive)(MDN)。

原始型別有

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol(ES6)

物件

原始型別在賦值時一般是直接傳值的方式(pass by value),如

let a = 5;
let b = a;
b = 10

console.log(a) //5
console.log(b) //10

但是物件會是傳指標(pass by Reference),如

let obj1 = { a:5, b:10, c:15};
let obj2 = obj1;
obj2.b = 20

console.log(obj1) //{ a:5, b:20, c:15} 改b卻影響a,因為是傳Reference
console.log(obj2) //{ a:5, b:20, c:15}

所以一般我們在[複製]物件(or 陣列)時會利用函式處理,而不會直接用等號賦值。但是在複製時,分為淺拷貝及深拷貝。

淺拷貝(Shallow Copy) VS 深拷貝 (Deep Copy)

可以參考這張圖

對Shallow copy來說,只是複製collection structure,而不是element。所以在指派第二層物件時會出現問題。

而Deep Copy是整個複製,包含element。所以當我們在使用多層物件時,要盡量用deep copy。

上網看到有人用英文解釋得很好

Shallow copies duplicate as little as possible. A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.

Deep copies duplicate everything. A deep copy of a collection is two collections with all of the elements in the original collection duplicated.

至於Collection代表的是:

Collections can be diverse data structures which stores multiple data items.

所以說,對於淺拷貝而言,第一層的原始型別是傳值,而物件的話則是傳遞REFERENCE。

let obj1 = {a:{b:5}};
let obj2 = {a:obj1.a};//都把a指派到{b:5}這個物件的reference
obj2.a.b = 10

console.log(obj1) //{a:{b:10}}; 被指派到10
console.log(obj2) //{a:{b:10}}; 
console.log(obj1 === obj2) //false 實際上不是同物件
console.log(obj1.a === obj2.a) //true 但是第二層物件實際上相同

所以當我們要確保物件也是整個複製,而不只只是複製reference時就需要用到Deep Copy

而一般我們在使用JS時,有哪些是屬於Shallow Copy,又有哪些是屬於Deep Copy呢?

Shallow Copy
  1. Array.concat:一般Array.concat的用法是合併兩個陣列,如
    var alpha = ['a', 'b', 'c'],
        numeric = [1, 2, 3];
    
    var alphaNumeric = alpha.concat(numeric);
    
    console.log(alphaNumeric); // 結果: ['a', 'b', 'c', 1, 2, 3]
  2. Array.slice一般Array.slice()的方法是複製一個新的陣列,帶入的參數為Array.slice(start, end),當不輸入參數值哲是直接複製一個。
    var animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
    
    console.log(animals.slice(2));
    // expected output: Array ["camel", "duck", "elephant"]
    
    console.log(animals.slice(2, 4));
    // expected output: Array ["camel", "duck"]
    
    console.log(animals.slice());
    //expected output: Array ['ant', 'bison', 'camel', 'duck', 'elephant'];
  3. Object .assignObject.assign用來合併物件,用法為Object.assign(target, ...source)若目標物件為空物件則可視為複製一個source的物件。
    var obj = { a: 1 };
    var copy = Object.assign({}, obj);
    console.log(copy); // { a: 1 }

Deep Copy

  1. JSON.parse(JSON.stringify(object_array))一次用到兩個函式:
    • JSON.parse():把字串轉成物件
    • JSON.stringify():把物件轉成字串。
    let obj1 = {a:{b:10}};
    let obj2_string = JSON.stringify(obj1);
    console.log(obj2_string); //"{"a":{"b":10}}";
    
    let obj2 = JSON.parse(obj2_string);
    console.log(obj2); //{a:{b:10}}
    
    obj2.a.b = 20;
    console.log(obj1); //{a:{b:10}}
    console.log(obj2); //{a:{b:20}}
  2. Jquery的extend
    // Shallow copy
        var objectIsNew = jQuery.extend({}, objectIsOld);
    // Deep copy
        var objectIsNew = jQuery.extend(true, {}, objectIsOld);
  3. Lodash的_.cloneDeep
    var objects = [{ 'a': 1 }, { 'b': 2 }];
     
    var deep = _.cloneDeep(objects);
    console.log(deep[0] === objects[0]);
    // => false

    相信經過以上的介紹之後,大家對於JS的pass by value 及 pass by reference更為熟悉了吧!