Site icon 134340號小行星

[JS學習筆記] JS 中的傳值、傳址與淺拷貝、深拷貝

javascript 740x374 1 [JS學習筆記] JS 中的傳值、傳址與淺拷貝、深拷貝

淺拷貝(shallow copy)、深拷貝(deep copy) 是在 JS 中經常被提到的一個知識點,也是面試時經常考的問題之一。我在項目中經常遇到因淺拷貝而引起的 BUG,所以花了點時間來了解一下淺拷貝與深拷貝,避免之後再因為淺拷貝引起「靈異事件」。

傳值與傳址

在 JavaScript 中的內建型別主要分為基本型別以及物件型別。

這兩種型別之間的傳值方式並不相同:基本型別傳值(call by value)物件型別傳址(call by reference)

基本型別: 傳值

基本型別相互比較時會比較值本身,所以下方例子屬嚴格相等:

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

再看另一個例子,a 為 19,並將 a 賦予給 b,b++ 會等於 20。

這是因為 number 屬於基本型別,而基本型別傳的是值,所以 b 會等於 19,19+1=20

const a = 19;
let b = a;
b++;
console.log(a, b); // 19, 20

物件型別: 傳址

每個物件都是獨立存在的實體,物件之間的記憶體位址並不相同。因此在比較物件型別時比較的是記憶體位址而非值。

const a = {
   str: 'hello,world'
};
const b = {
   str: 'hello,world'
};
console.log(a === b); // false

並且,如果將一個物件賦予給另一個變數時,就會將物件的記憶體位址傳給該變數:

const a = {
   str: 'hello,world'
};

let b = a;

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

所以當你在修改 b 的屬於與值時,a 也會發生變化:

let a = {
   str: 'hello,world'
};
let b = a;

b.id = 2;

console.log(a, b); // { id: 2, str: "hello,world" }, {id: 2, str: "hello,world" }

淺拷貝與深拷貝

那麼什麼是淺拷貝什麼是深拷貝呢?

複製物件方式分為以下兩種:

淺拷貝的方式

Object.assign()

Object.assign() 被用來複製一個或多個物件自身所有可數的屬性到另一個目標物件。回傳的值為該目標物件。

當你的物件有多層結構時,Object.assign() 就會是淺拷貝,舉個例子:

var obj = {
  id: 1,
  data: [
    { id: 2, name: 'test' }
  ]
};
var obj2 = Object.assign({}, obj);

obj2.data[0].id = 3;

console.log(obj, obj2); // { id: 1, data: [{id: 3, name: 'test'}]},{id: 1,data: [{id: 3, name: 'test'}]

但要注意的是 Object.assign 在物件只有一層的時候是深拷貝

var obj = { a: 1 };
var obj2 = Object.assign({}, obj);

obj2.b = 2;

console.log(obj, obj2); // { a: 1 }, { a: 1, b: 2 }

擴展運算子

還有就是擴展運算子(Spread Operator),不過跟 Object.assign() 一樣只有在多層結構時才是淺拷貝:

const obj = {
  group: 1,
  child: [
    { id: 1, name: 'test' },
    { id: 2, name: 'happy' }
  ]
}

const obj2 = { ...obj };

obj2.child[1].id = 3;

console.log(obj, obj2);
/* { group: 1, child: [{ id: 1, name: 'test' },{ id: 3, name: 'happy' }]},
 { group: 1, child: [{ id: 1, name: 'test' },{ id: 3, name: 'happy' }]} */

一旦物件中只有一層結構時就會是深拷貝

const obj = {
   id: 1,
   name: 'test'
}

const obj2 = { ...obj };

obj2.age = 30;

console.log(obj, obj2); //{ id: 1, name: "test" }, { age: 30, id: 1, name: "test" }

其他如:concat(), slice() 也屬於淺拷貝,這邊就不展開講了。

深拷貝的方式

如前面所說,如果物件僅只有一層時,Object.assign() 以及擴展運算子都可以實現淺拷貝。

JSON.parse 和 JSON.stringify

最常見的違深拷貝的方式,先將物件轉為字串,再將字串轉為新的物件:

const obj = { id: 1, name: 'test' }
const obj2 = JSON.parse(JSON.stringify(obj));

obj2.id = 3;

console.log(obj, obj2); // { id: 1, name: "test" }, { id: 3, name: "test" }

但這樣做的話,一旦遇到 function、map 以及 set…等型別時就會失效。

如果非要複製 function,可以考慮使用 prototype 而不是直接拷貝。

Object.create()

使用 Object.create() 一樣可以達到深拷貝的效果:

const obj = { id: 1, name: 'test', func: function() { return 1 } }
const obj2 = Object.create(obj);

obj2.func = function() { return 2 };

console.log(obj.func(), obj2.func()); // 1, 2

除此之外,lodash 這個知名的 JavaScript 函式庫也有提供 _.cloneDeep() 方法來深拷貝。

Exit mobile version