iT邦幫忙

0

Javascript 傳值傳址&深淺拷貝

前言

因為公司前端資料已經處理成單層結構,所以都沒注意到淺拷貝、深拷貝的實際差別。
在讀完高手文章後,才發現和自己想的不一樣。
也順手將文章重點整理,分享給大家,別枉費自己寫這麼多Code/images/emoticon/emoticon02.gif

重點

  • 基本型別 ⇒ 傳值。stringnumberbooleannullundefined
  • 物件型別 ⇒ 傳址
  • 淺拷貝 Shallow Copy ⇒ 物件若有多層,第一層之下的物件會以傳址拷貝。
    • 物件的淺拷貝方法
      • Object.assign()
      • ...
    • 陣列的淺拷貝方法
      • array.slice()
      • array.concat()
      • array.map()
      • array.filter()
      • forEach +push()
      • ...
  • 深拷貝 Deep Copy ⇒ 返回一個全新的物件且不與被複製物件有關係。
    • 物件、陣列深拷貝方式
      • JSON.parse(JSON.stringify())
      • 第三方函示庫,Lodash ⇒ _.cloneDeep() ...等等

傳值

var a = 10;
var b = 10;

console.log(a === b); // true

ab皆為基本型別,故互相比較時回傳true

傳址

var obj1 = { a : 1 };
var obj2 = { a : 1 };

console.log(obj1 === obj2); // false

雖然物件內容相等,但是物件的記憶體位置卻不同,故比較結果為false

不同的例子

var obj1 = { a : 1 };
var obj2 = obj1;

obj1.a = 0;
console.log(obj1.a); // 0
console.log(obj2.a); // 0

obj2.a = 2;
console.log(obj1.a); // 2
console.log(obj2.a); // 2

console.log(obj1 === obj2); // true

當我們修改任何一邊的屬性時,會一起變動,這是因為物件透過傳址的方式指派,所以兩個物件會指向同一個記憶體位置,實際上也意味著並未產生新的物件。

但若是這樣呢?

var obj1 = { a : 1 };
var obj2 = obj1;

obj1.a = 0;
console.log(obj1.a); // 0
console.log(obj2.a); // 0

obj2.a = 2;
console.log(obj1.a); // 2
console.log(obj2.a); // 2

obj1 = {}; // 指派新的物件

console.log(obj1 === obj2); // false

obj1 被指向新的記憶體位置,但是obj2 依然保持原本的記憶體位置,因此這時obj1obj2 彼此就毫無關係了。

複製物件

複製物件可以分為兩種

  • 淺拷貝 Shallow Copy
  • 深拷貝 Deep Copy

淺拷貝

  1. 物件的淺拷貝

    • Object.assign()
    • 展開運算子...

    Object.assign(target, source) 能複製一個或多個物件自身所有可數的屬性到另一個目標物件。

    將原本的obj 內容複製到另一個空物件

    var obj = { a : 1, b : 2 };
    
    var obj2 = Object.assign({}, obj);
    
    console.log(obj2); // { a : 1, b : 2 }
    console.log(obj == obj2); // false
    

    展開運算子

    var obj = { a : 1, b : 2 };
    var obj2 = { ...obj1 };
    
    console.log(obj2); // { a : 1, b : 2 }
    console.log(obj === obj2); // false
    
  2. 陣列的淺拷貝

    • array.slice()
    • array.concat()
    • array.map()
    • array.filter()
    • forEach +push()
    • ...

    slice() 原本是用在分割陣列,但用參數為0或不傳入的話,相當於淺拷貝

    var arr = [1, 2, 3, 4];
    
    var arr2 = arr;
    console.log(arr === arr2); // true
    
    var arr3 = arr.slice(0); 
    
    console.log(arr === arr3); // false
    console.log(arr3); // [1, 2, 3, 4]
    

    concat() 原本是用在組合陣列,但可以使用空陣列合併,也相當於淺拷貝

    var arr = [1, 2, 3, 4];
    
    var arr2 = [].concat(arr);
    
    console.log(arr === arr2); // false
    consoloe.log(arr2); // [1, 2, 3, 4]
    

    map() 是將執行結果會存至新陣列,若回傳原本的元素,也相於淺拷貝

    var arr = [1, 2, 3, 4];
    
    var arr2 = arr.map(x => x);
    
    console.log(arr === arr2); // false
    console.log(arr2); // [1, 2, 3, 4]
    

    filter 是將符合條件的值存至新陣列,若條件皆為true ,也相當於淺拷貝

    var arr = [1, 2, 3, 4];
    var arr2 = arr.filter(x => { return true; } );
    
    console.log(arr === arr2); // false
    console.log(arr2); // [1, 2, 3, 4]
    

    ... 也可用於陣列,效果也是淺拷貝

    var arr = [1, 2, 3, 4];
    
    var arr2 = [ ...arr ];
    
    console.log(arr === arr2); // false
    console.log(arr2); // [1, 2, 3, 4]
    

    至於為什麼叫淺拷貝,可以看以下程式碼

    var obj1 = {
      foo : 10,
      bar : {
        baz : 20,
      },
    };
    
    var obj2 = { ...obj1 };
    
    console.log(obj1 === obj2); // false
    
    console.log(obj1.bar === obj2.bar); // true
    

    淺拷貝後,obj1obj2 已經是兩個不同的物件,但是第二層的物件卻是相同的記憶體位置。

    再看看陣列

    var arr = [{a : 1}, {b : 2}];
    
    var arr2 = [...arr];
    
    console.log(arr === arr2); // false
    
    console.log(arr[0] === arr2[0]); // true
    

    淺拷貝後,arrarr2 已經是不同的陣列,但是裡面的物件卻是同一個。

    上述只是有容器不同,若有巢狀或多維的狀況,仍然是傳址。因為深層的物件或陣列還是傳址,不會完全複製一份,所以稱為『淺拷貝』。

    深拷貝

    深拷貝是完全複製一份新的,不會共用記憶體位置。

    • Json.parse(Json.stringify())
    • 第三方函示庫,Lodash ⇒ _.cloneDeep() ...等等

    利用JSON

    var obj1 = {
    	a : 1,
    	b : {
    		val: 5
    	}
    };
    
    var obj2 = JSON.parse(JSON.stringify(obj1));
    
    console.log(obj1 === obj2); // false
    console.log(obj1.b === obj2.b); // false
    

    但JSON的轉法須注意

    • 函式、undefinedSymbol 會被忽略轉換
    • NaNInfinity 會被轉換成 null
    var obj1 = {
      a: function() {},
      b: undefined,
      c: Symbol(''),
      d: NaN,
      e: Infinity,
      f: -Infinity,
    };
    
    var obj2 = JSON.parse(JSON.stringify(obj1));
    
    console.log(obj2);  // {d: null, e: null, f: null}
    

參考

參考文章


尚未有邦友留言

立即登入留言