# Explain the difference between mutable and immutable objects. 本文作者學習筆記網站:https://dev.rraiy.cc/ --- ![](https://i.imgur.com/ioCY23s.png) 1. 不可變性在函式編程領域裡是一個核心概念,尤其對於物件導向帶來許多益處。 1. mutable object 表示 物件創建之後,狀態可以被更動。反之,immutable object 無法被更動。在 JS 裡,Number、String 屬於不可變的資料類型,但自定義的物件通常是可變的。 2. (*)哪些資料型別是 immutable: 包括 Number, String, null, undefined 和 Boolean 等型別(有 by value 特性) 在修改時,是在記憶體內部重新分配一個區塊存放新的資料,而資料名字就指向新分配的記憶體區塊,原本舊的記憶體區塊還是放著舊值,所以就叫 immutable (*)哪些資料型別是 mutable: 包括 Object, Array, Function, Map 和 Set 等型別(有 by reference 特性) 在實際修改物件的值時,將會修改到物件參考到的記憶體位置內容,所以稱為 mutable 3. 內建的不可變物件方法舉例:Math、Date * React 的 state 也是 immutable ### 不可變性的優缺點 優點: 1. 容易測試、回推、減少副作用 2. 不需要預防性拷貝 3. 重複使用、多執行緒也不用擔心會影響原物件 缺點: 1. 相比可變物件,拷貝物件會添增內存消耗,因為必須產生新的實例 → 建議使用第三方庫來達成高效的運作。 2. 為了一堆物件的配置以及解除配置導致性能差,記憶體內分配器的複雜程度是取決於 heap 中的物件數量。(分配器就是用於處理容器對記憶體的分配與釋放請求) heap memory allocation is complex 3. 如果要構建循環結構(例如 Graph 結構),沒辦法讓兩個物件相互指向 ![](https://i.imgur.com/ajhxoJB.png) ### 怎麼樣才能做/模擬 immutable object? * 套件 * Object methods :::warning 需要先了解 JS 物件屬性的差別,才能理解 methods 的作用 訪問器類型 vs. 數據類型 的屬性 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures 在 JS 裡,物件對象被視為屬性(property)的集合,最初 JS 用 物件實字來定義一個物件時,會自動初始化屬性方法 「每個屬性(property)都有對應的特性(attribute),這些 attribute 是JS引擎內部在使用的」 ```javascript= var a = "Hello" // a 具有字串的方法 像是 length var b = {} // b 具有hasOwnProperty、constructor ``` ::: 套件: 1. ImmmutableJS (網上相關文章很多,很常用來跟React搭配,以後有機會深入研究再跟大家分享) 2. seamless-immutable 這篇主要介紹以 **Object methods** 來達成 :::danger 以下方法在嘗試不允許的操作時,非嚴格模式都會默認失敗、嚴格模式會拋出 TypeError,所以還不熟悉的情況下,建議以嚴格模式練習 ::: :::spoiler Object Constant Properties 藉由 Object.defineProperty() 方法 搭配兩個關鍵的屬性定義「writable、configurable」,可以創建常量 (const) 物件方法 *常量物件:無法被改變、重新定義、刪除 舉例 ```javascript= let myObject = {}; Object.defineProperty(myObject, 'number', { value: 42, writable: false, // 不可被賦值運算子改變 configurable: false, // 不可改變、不可刪除 }); console.log(myObject.number); // 42 myObject.number = 43; console.log(myObject.number); // 42 ``` ::: :::spoiler Prevent Extensions 藉由 Object.preventExtension() ,防止物件被新增新的屬性 ```javascript= var myObject = { a: 2, }; Object.preventExtensions(myObject); myObject.b = 3; myObject.b; // undefined ``` ::: :::spoiler Seal Object.seal() 密封物件 可以修改舊的屬性值 不能刪除舊屬性 不能增加新屬性 原是訪問器屬性不可變為數據屬性,原是數據屬性不可變為訪問器屬性 ( configurable ) ```javascript= var obj = { prop: function() {}, foo: 'bar' }; // 可以添加新的屬性 // 可以更改或刪除現有的屬性 obj.foo = 'baz'; obj.lumpy = 'woof'; delete obj.prop; // 一旦被密封後 var o = Object.seal(obj); o === obj; // true Object.isSealed(obj); // === true // 仍然可以修改密封物件的屬性值 obj.foo = 'quux'; // 但不能修改原本具有的訪問器、數據屬性 Object.defineProperty(obj, 'foo', { get: function() { return 'g'; } }); // throws a TypeError // 除了修改原本的屬性值,其他操作都會失敗 // 添加失敗 obj.quaxxor = 'the friendly duck'; // 刪除失敗 delete obj.foo; // 嚴格模式下會拋出明顯錯誤,一般模式會默認失敗 function fail() { 'use strict'; delete obj.foo; // throws a TypeError obj.sparky = 'arf'; // throws a TypeError } fail(); // 就算用 Object.defineProperty 添加新屬性也會失敗 Object.defineProperty(obj, 'ohai', { value: 17 }); // throws a TypeError Object.defineProperty(obj, 'foo', { value: 'eit' }); // 但可以 Object.defineProperty 修改舊屬性值 ``` * 跟 Object.freeze() 不同的是,freeze() 後物件的舊屬性值是不可變,而 seal() 可以 ::: :::spoiler Freeze Object.freeze() 凍結物件 不能修改舊有的屬性值 ( 所有數據屬性會被設定 writable:false ) 新增、刪除一律不準 可列舉性、可設定性、可寫性、物件原型都一起被凍結 舉例 - 凍結物件 ```javascript= var obj = { prop: function() {}, foo: 'bar' }; // 可以新增屬性,舊有屬性可以改變或刪除 obj.foo = 'baz'; obj.lumpy = 'woof'; delete obj.prop; // 回傳的物件跟原本傳入的物件是同一個,所以不需要記住回傳值就能凍結 Object.freeze(obj); console.log(Object.isFrozen(obj)); // === true // 現在任何改動都會失敗 obj.foo = 'quux'; // 默認失敗 // 屬性無法被新增 obj.quaxxor = 'the friendly duck'; // 默認失敗 // 在嚴格模式中,以上嘗試都會丟出 TypeError function fail(){ 'use strict'; obj.foo = 'sparky'; // 丟出 TypeError delete obj.foo; // 丟出 TypeError delete obj.quaxxor; // 回傳 true 因為屬性 'quaxxor' 從來沒有被新增 obj.sparky = 'arf'; // 丟出 TypeError } fail(); // 嘗試透過 Object.defineProperty 來改變屬性的值會丟出 TypeError Object.defineProperty(obj, 'ohai', { value: 17 }); // Cannot define property ohai, object is not extensible at Function.defineProperty Object.defineProperty(obj, 'foo', { value: 'eit' }); // Cannot redefine property // 一樣不能改變物件的原型,都會丟出 TypeError Object.setPrototypeOf(obj, { x: 20 }) obj.__proto__ = { x: 20 } ``` ::: --- 本文作者學習筆記網站:https://dev.rraiy.cc/ --- 參考文章 該文所有方法的 MDN [https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) [https://stackoverflow.com/questions/1863515/pros-cons-of-immutability-vs-mutability](https://stackoverflow.com/questions/1863515/pros-cons-of-immutability-vs-mutability) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures https://snh90100.medium.com/%E7%90%86%E8%A7%A3-mutable-vs-immutable-%E7%89%A9%E4%BB%B6-20ed802b6283