# 物件與記憶體位置 ## 物件傳參考特性 - 陣列、函式、物件、日期都屬於物件型別 - 物件型別屬於傳參考 ```javascript const person = { name: "小明", }; const person2 = person; person2.name = "漂亮阿姨"; //漂亮阿姨 console.log(person.name); ``` ```javascript const person = { name: "小明", }; function fn(obj) { obj.name = "漂亮阿姨"; } fn(person); //漂亮阿姨 console.log(person.name); ``` 當 key 名稱存在於變數中時,可使用中括號取用: ```javascript var variable = 'go'; obj[variable](); ``` ### 進階觀念 - 物件中的屬性名稱本質上都是字串 - 可以加上?.可選串聯,防止存取不存在的屬性時拋錯 - 特殊字元(如 emoji)可作為 key,但需用字串形式包起來 - 可用變數作為 key,只要加上中括號 [] ```javascript const propertyName = "name"; const obj = { [propertyName]: "小明", 姓名: "小華", 1: 1, '❤️': "這是一個 emoji", inner: { 姓名: "內層的物件" } }; console.log(obj); console.log(obj.test?.name); // undefined,不拋錯 ``` ## 函式也是物件 函式屬於物件型別,因此可以使用物件的方式賦值屬性 ```javascript const fn = function () {}; const fn2 = fn; fn2.age = 18; console.dir(fn); console.log(fn.age); ``` ## parseInt相關補充 Number.parseInt 和全域的 parseInt 指向同一個記憶體位置 ```javascript //100 console.log(Number.parseInt("100元")); //true console.log(window.parseInt === Number.parseInt); //true console.log(parseInt("100元")); ``` 將內建函數parseInt賦值給一個常數變數"轉數值"。轉數值這個變數就指向parseInt函數 ```javascript const 轉數值 = parseInt; console.log(轉數值("100元")); ``` 將 parseInt 指向 null,不影響 Number.parseInt ```javascript parseInt = null; console.log(Number.parseInt("100元")); ``` ## 物件參考範例說明 函式內直接修改傳入物件的屬性,會影響原物件 ```javascript const family = { name: "小明家", Ming: { name: "小明", }, }; const Ming = family.Ming; function fn(obj) { obj.name = "漂亮阿姨"; } fn(Ming); //{name : '漂亮阿姨'} console.log(Ming); ``` 若函式內重新賦值一個新物件,則不影響原本的物件: 1. fn(Ming) 傳入的是 Ming 的參考(指向原本那個 { name: "小明" } 的記憶體位址) 2. 但在函數內你寫了 obj = { name: "杰倫" },這行讓 obj 改指向了一個新的物件 3. 此時 obj 跟原本的 Ming 就沒有任何關係了 4. 所以你後面改的是 obj 指向的新物件,對 Ming 沒有影響 ```javascript const family = { name: "小明家", Ming: { name: "小明", }, }; const Ming = family.Ming; function fn(obj) { obj = { name: "杰倫", }; obj.name = "漂亮阿姨"; } fn(Ming); console.log(Ming); ``` ## 淺層拷貝 - 只複製第一層屬性 - 對於物件中的基本類型(primitive)值,會直接複製值 - 對於物件中的參考類型(object、array、function)值,只複製參考(reference) **常見方法:** - Object.assign({}, obj) - 展開運算子:{ ...obj }、[ ...arr ] ```javascript const family = { name: "小明家", Ming: { name: "小明" }, }; const newFamily = { ...family }; newFamily.name = "小華"; newFamily.Ming.name = "漂亮阿姨"; console.log(newFamily); console.log(family); ``` ## 深層拷貝 - 完整複製所有層級的資料,不共用記憶體位置 - 佔用較多記憶體,但避免資料意外互相影響 ```javascript const family = { name: "小明家", member: { name: "小明", }, }; const newFamily = JSON.parse(JSON.stringify(family)); newFamily.name = "小華家"; newFamily.member.name = "漂亮阿姨"; console.log(family); console.log(newFamily); ``` ## 函式參數重新賦值 **當函式內部 obj = {...},只會改變內部參數的參考,不影響原始物件:** - 當你在函式內 obj = { name: 1 } 時,是在「重新賦值給參數變數 obj」,這個改變只發生在 fn 函式的內部作用域,不會影響外部的 person 變數所指的記憶體位址 - obj會另外創建另一個記憶體空間 ```javascript const person = {}; function fn(obj) { obj = { name: 1, }; } fn(person); console.log(person); ``` ## 補充:物件搭配陣列方法 物件可透過 Object.keys 和 Object.values 使用陣列方法: ```javascript const family = { Ming: { name: '小明' }, Jay: { name: '杰倫' }, Auntie: { name: '漂亮阿姨' } }; console.log(Object.keys(family)); // ['Ming', 'Jay', 'Auntie'] console.log(Object.values(family)); // [ {...}, {...}, {...} ] Object.values(family).forEach(item => { console.log(item); }); Object.keys(family).forEach(key => { console.log(key); console.log(family[key]); }); ```