正在載入索引…

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

4.5k 字,約 5 分鐘

文章目錄
  1. Javascript 型別說明
  2. 原始型別(Primitive Types)
  3. 物件資料(Object data)
  4. javascript-原始型別-傳值(Pass by value)
  5. javascript-物件-傳參考(Pass by reference)
  6. 淺拷貝(Shallow copy)
  7. 淺拷貝的方式
    1. ...展開運算子(Spread operator)
    2. 使用 Object.assign()的方法
    3. 陣列能夠使用 slice 的方法
    4. 手動複製物件中其中一層資料
  8. 深拷貝(Deep Copy)
  9. 深拷貝的方式
    1. 使用 JSON.parse() 和 JSON.stringify()
    2. 使用第三方庫(例如 Lodash)

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

  • 宣告方式如下
javascript
// ↓數字
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)
javascript
// ↓物件 (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)

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

顯示如下:

javascript
100

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

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

顯示如下:

javascript
{ food: 'banana' }

淺拷貝(Shallow copy)

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

顯示如下:

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

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

顯示如下:

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

淺拷貝的方式

...展開運算子(Spread operator)

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

使用 Object.assign()的方法

javascript
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)

顯示如下:

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

陣列能夠使用 slice 的方法

javascript
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);

顯示如下:

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

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

javascript
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)

顯示如下

javascript
改變前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()

javascript
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)

顯示如下:

javascript
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 會被忽略,不會被序列化
javascript
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);

顯示如下

javascript
undefineValue undefined
NaNValue null
InfinityValue null
regExpValue {}
dateValue 1999-12-31T15:59:59.000Z
function undefined
symbolValue undefined

使用第三方庫(例如 Lodash)

javascript
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)

顯示如下:

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

留言

留言使用 GitHub 帳號登入(Giscus),留言會存在 GitHub Discussions。