JavaScript(5):物件傳參考特性(Pass by reference)

Blog背景圖

JS-5 Javascript物件傳參考特性

JS的型別大致可分為原始型別(Primitive Types)及物件(Object)兩種

  • 原始型別(Primitive Types):傳值(Pass by value)
    • 本身不可變
  • 物件(Object):傳參考(Pass by reference)
    • 本身可變

原始型別(Primitive Types)

  • Number
    • 整數
    • 含小數點
    • Infinity(無窮大)
    • -Infinity(負無窮大)
    • NaN(Not A Number)—>不是數值
  • BigInt
  • String
  • Boolean
    • true
    • false
  • Undefined
  • Null
  • Symbol

  • 宣告方式如下
1
2
3
4
5
6
7
8
9
10
11
12
13
// ↓數字
let num = 42;
// ↓字串
let str = "Hello, World!";
// ↓布林
let isTrue = true;
let isFalse = false;
// ↓未定義
let undefinedVar;
// ↓空值
let nullVar = null;
// ↓符號
let sym = Symbol("uniqueSymbol");

物件資料(Object data)

  • 物件 (Object)
  • 陣列 (Array)
  • 函數 (Function)
1
2
3
4
5
6
7
8
9
10
11
12
// ↓物件 (Object)
let person = {
name: "John",
age: 30,
isStudent: false
};
// ↓陣列 (Array)
let fruits = ["apple", "orange", "banana"];
// ↓函數 (Function)
function greet(name) {
console.log("Hello, " + name + "!");
}

javascript-原始型別-傳值(Pass by value)

  • 傳遞原始型別的值時,是按值傳遞的。函數接收的是原始值的一個複本,對這個複本的修改不會影響原始值。
1
2
3
4
let a = 100
let b = a
b = 200
console.log(a)

顯示如下:

1
100

javascript-物件-傳參考(Pass by reference)

  • 當傳遞物件(Object)或陣列(Array)等引用型別的值時,函數接收的是物件或陣列的參考,對這個參考的修改會影響原始物件或陣列。
1
2
3
4
let a = {"food": "apple"}
let b = a
b.food = "banana"
console.log(a)

顯示如下:

1
{ food: 'banana' }

淺拷貝(Shallow copy)

  • 只拷貝被複製物件的第一層屬性,而不是整個物件及其嵌套的屬性。換句話說,淺拷貝創建了一個新的物件,並將原始物件的直接屬性複製到新的物件中,但對於原始物件中的物件(如物件中的物件),它們仍然是引用,而不是被複製的。
  • 成功複製第一層物件的屬性,且彼此互不影響。
1
2
3
4
5
let a = { "food": "apple" }
let b = { ...a }
b.food = "banana"
console.log("a變數= ", a)
console.log("b變數= ", b)

顯示如下:

1
2
a變數=  { food: 'apple' }
b變數= { food: 'banana' }

  • 物件中的物件因為指向同一記憶體位址,因此彼此變動會連動。
1
2
3
4
5
let a = { "food": "apple", obj : {"feeling": "happy"} }
let b = { ...a }
a.obj.feeling = "sad"
console.log("a變數= ", a)
console.log("b變數= ", b)

顯示如下:

1
2
a變數=  { food: 'apple', obj: { feeling: 'sad' } }
b變數= { food: 'apple', obj: { feeling: 'sad' } }

淺拷貝的方式

…展開運算子(Spread operator)

  • 上面的案例即是採用展開運算子

使用Object.assign()的方法

1
2
3
4
5
6
const originalObject = { a: 1, b: { c: 2 } };
const shallowCopiedObject = Object.assign({}, originalObject);
shallowCopiedObject.a = 2
shallowCopiedObject.b.c = 100000
console.log("originalObject", originalObject)
console.log("shallowCopiedObject", shallowCopiedObject)

顯示如下:

1
2
originalObject { a: 1, b: { c: 100000 } }
shallowCopiedObject { a: 2, b: { c: 100000 } }

陣列能夠使用slice的方法

1
2
3
4
5
6
const originalArray = [1, [2, 3], 4];
const shallowCopiedArray = originalArray.slice();
shallowCopiedArray[0] = 'X1';
shallowCopiedArray[1][0] = 'X2';
console.log("originalArray", originalArray);
console.log("shallowCopiedArray", shallowCopiedArray);

顯示如下:

1
2
originalArray [ 1, [ 'X2', 3 ], 4 ]
shallowCopiedArray [ 'X1', [ 'X2', 3 ], 4 ]

手動複製物件中其中一層資料

1
2
3
4
5
6
7
8
9
let a = { "food": "apple", obj: { "feeling": "happy" } }
let b = { "food": a.food, obj: a.obj }
console.log("改變前a", a)
console.log("改變前b", b)
b.food = "banana"
b.obj.feeling = "sad"
console.log("------------")
console.log("改變後a", a)
console.log("改變後b", b)

顯示如下

1
2
3
4
5
改變前a { food: 'apple', obj: { feeling: 'happy' } }
改變前b { food: 'apple', obj: { feeling: 'happy' } }
------------
改變後a { food: 'apple', obj: { feeling: 'sad' } }
改變後b { food: 'banana', obj: { feeling: 'sad' } }

深拷貝(Deep Copy)

  • 是指創建一個新的物件,並且這個新物件的所有屬性的值都是原始物件相對應屬性值的複本。深拷貝會遞迴地複製原始物件及其所有嵌套的物件,使得新的物件和原始物件在記憶體中獨立存在,修改其中一個物件不會影響另一個。

深拷貝的方式

使用 JSON.parse() 和 JSON.stringify()

1
2
3
4
5
6
const originalObject = { a: 1, b: { c: 2 } };
const deepCopiedObject = JSON.parse(JSON.stringify(originalObject));
deepCopiedObject.a = 200
deepCopiedObject.b.c = 4000
console.log("originalObject", originalObject)
console.log("deepCopiedObject", deepCopiedObject)

顯示如下:

1
2
originalObject { a: 1, b: { c: 2 } }
deepCopiedObject { a: 200, b: { c: 4000 } }

需注意使用此方式會有些意外情況如下

  • undefined:undefined的值會在序列化過程中完全丟失,包括包含 undefined 值的鍵本身。
  • NaN:NaN會被強制轉換成null。
  • Infinity和-Infinity:這兩個特殊的數字值會被強制轉換成 null。
  • 正則表達式(RegExp):正則表達式會被強制轉換為空物件
  • 日期(Date):日期會被轉換成對應的字符串形式。
  • 函數(Function):函數是無法被序列化的,它們會被忽略。
  • 符號Symbol 會被忽略,不會被序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const originalData = {
undefinedValue: undefined,
notANumberValue: NaN,
infinityValue: Infinity,
regExpValue: /.*/,
dateValue: new Date('1999-12-31T23:59:59'),
functionValue: function() {},
symbolValue: Symbol('uniqueSymbol')
};
const faultyClonedData = JSON.parse(JSON.stringify(originalData));
console.log("undefineValue", faultyClonedData.undefinedValue);
console.log("NaNValue", faultyClonedData.notANumberValue);
console.log("InfinityValue", faultyClonedData.infinityValue);
console.log("regExpValue", faultyClonedData.regExpValue);
console.log("dateValue", faultyClonedData.dateValue);
console.log("function", faultyClonedData.functionValue);
console.log("symbolValue", faultyClonedData.symbolValue);

顯示如下

1
2
3
4
5
6
7
undefineValue undefined
NaNValue null
InfinityValue null
regExpValue {}
dateValue 1999-12-31T15:59:59.000Z
function undefined
symbolValue undefined

使用第三方庫(例如 Lodash)

1
2
3
4
5
6
7
const _ = require('lodash');
const originalObject = { a: 1, b: { c: 2 } };
const deepCopiedObject = _.cloneDeep(originalObject);
deepCopiedObject.a = 30
deepCopiedObject.b.c = 4000
console.log("originalObject", originalObject)
console.log("deepCopiedObject", deepCopiedObject)

顯示如下:

1
2
originalObject { a: 1, b: { c: 2 } }
deepCopiedObject { a: 30, b: { c: 4000 } }