Immutability เป็นหลักการหลักในการเขียนโปรแกรมเชิงฟังก์ชัน และมีประโยชน์มากมายสำหรับโปรแกรมเชิงวัตถุด้วย Mutable object คือออบเจกต์ที่สถานะสามารถแก้ไขได้หลังจากสร้างขึ้น Immutable object คือออบเจกต์ที่สถานะไม่สามารถแก้ไขได้หลังจากสร้างขึ้น
อะไรคือตัวอย่างของ immutable object ใน JavaScript?
ใน JavaScript ชนิดข้อมูลบางชนิดที่สร้างมาพร้อม (ตัวเลข, สตริง) เป็น immutable แต่ custom object โดยทั่วไปเป็น mutable
immutable JavaScript objects ที่สร้างมาพร้อมบางตัวคือ Math, Date
ต่อไปนี้คือวิธีบางอย่างในการเพิ่ม/จำลอง immutability บน plain JavaScript objects
Object Constant Properties
โดยการรวม writable: false และ configurable: false คุณสามารถสร้างค่าคงที่ (ไม่สามารถเปลี่ยนแปลง, กำหนดใหม่ หรือลบได้) เป็นคุณสมบัติของออบเจกต์ เช่น:
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
Prevent Extensions
หากคุณต้องการป้องกันไม่ให้ออบเจกต์มีคุณสมบัติใหม่เพิ่มเติมเข้ามา แต่ยังคงคุณสมบัติอื่นๆ ของออบเจกต์ไว้ตามเดิม ให้เรียกใช้ Object.preventExtensions(...):
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
ใน non-strict mode การสร้าง b จะล้มเหลวอย่างเงียบๆ ใน strict mode จะโยน TypeError
Seal
Object.seal() สร้างออบเจกต์ที่ "sealed" ซึ่งหมายความว่ามันรับออบเจกต์ที่มีอยู่และโดยพื้นฐานแล้วเรียก Object.preventExtensions() บนมัน แต่ยังทำเครื่องหมายคุณสมบัติที่มีอยู่ทั้งหมดเป็น configurable: false ด้วย
ดังนั้น คุณไม่เพียงแต่ไม่สามารถเพิ่มคุณสมบัติใดๆ ได้อีก แต่คุณยังไม่สามารถกำหนดค่าใหม่หรือลบคุณสมบัติที่มีอยู่ได้ (แม้ว่าคุณจะยังคงสามารถแก้ไขค่าของพวกมันได้)
Freeze
Object.freeze() สร้างออบเจกต์ที่ถูกตรึง ซึ่งหมายความว่ามันรับออบเจกต์ที่มีอยู่และโดยพื้นฐานแล้วเรียก Object.seal() บนมัน แต่ยังทำเครื่องหมายคุณสมบัติ "data accessor" ทั้งหมดเป็น writable:false เพื่อไม่ให้ค่าของพวกมันเปลี่ยนแปลงได้
แนวทางนี้เป็นระดับสูงสุดของ immutability ที่คุณสามารถทำได้สำหรับออบเจกต์เอง เนื่องจากมันป้องกันการเปลี่ยนแปลงใดๆ กับออบเจกต์หรือคุณสมบัติโดยตรงใดๆ ของมัน (แม้ว่า ดังที่กล่าวไว้ข้างต้น เนื้อหาของออบเจกต์อื่นที่อ้างอิงจะไม่ได้รับผลกระทบ)
var immutable = Object.freeze({});
การตรึงออบเจกต์ไม่อนุญาตให้เพิ่มคุณสมบัติใหม่ให้กับออบเจกต์ และป้องกันการลบหรือแก้ไขคุณสมบัติที่มีอยู่ Object.freeze() รักษาความสามารถในการระบุ, การกำหนดค่า, ความสามารถในการเขียน และ prototype ของออบเจกต์ มันคืนค่าออบเจกต์ที่ส่งผ่านและไม่สร้างสำเนาที่ถูกตรึง
ข้อดีและข้อเสียของ immutability มีอะไรบ้าง?
ข้อดี
- ตรวจจับการเปลี่ยนแปลงได้ง่ายขึ้น - ความเท่าเทียมกันของออบเจกต์สามารถพิจารณาได้อย่างมีประสิทธิภาพและง่ายดายผ่านความเท่าเทียมกันของการอ้างอิง สิ่งนี้มีประโยชน์สำหรับการเปรียบเทียบความแตกต่างของออบเจกต์ใน React และ Redux
- โปรแกรมที่มี immutable objects คิดง่ายกว่า เนื่องจากคุณไม่ต้องกังวลว่าออบเจกต์อาจมีการเปลี่ยนแปลงเมื่อเวลาผ่านไป
- ไม่จำเป็นต้องมีการสำเนาป้องกันอีกต่อไปเมื่อ immutable objects ถูกส่งคืนจากหรือส่งผ่านไปยังฟังก์ชัน เนื่องจากไม่มีความเป็นไปได้ที่ immutable object จะถูกแก้ไขโดยมัน
- การแชร์ผ่านการอ้างอิงทำได้ง่าย - การคัดลอกออบเจกต์หนึ่งก็ดีเท่ากับอีกชุดหนึ่ง ดังนั้นคุณสามารถแคชออบเจกต์หรือนำออบเจกต์เดียวกันกลับมาใช้ซ้ำได้หลายครั้ง
- Thread-safe - Immutable objects สามารถใช้งานได้อย่างปลอดภัยระหว่างเธรดในสภาพแวดล้อมแบบ multi-threaded เนื่องจากไม่มีความเสี่ยงที่พวกมันจะถูกแก้ไขในเธรดที่รันพร้อมกันอื่นๆ
- การใช้ไลบรารีเช่น ImmutableJS ออบเจกต์จะถูกแก้ไขโดยใช้ structural sharing และต้องการหน่วยความจำน้อยลงสำหรับการมีหลายออบเจกต์ที่มีโครงสร้างคล้ายกัน
ข้อเสีย
- การใช้งานโครงสร้างข้อมูล immutable และการดำเนินการที่ขาดความรอบคอบอาจส่งผลให้ประสิทธิภาพต่ำมากเนื่องจากมีการสร้างออบเจกต์ใหม่ทุกครั้ง แนะนำให้ใช้ไลบรารีสำหรับโครงสร้างข้อมูล immutable ที่มีประสิทธิภาพและการดำเนินการที่ใช้ประโยชน์จากการแบ่งปันโครงสร้าง
- การจัดสรร (และยกเลิกการจัดสรร) ออบเจกต์ขนาดเล็กจำนวนมาก แทนที่จะแก้ไขออบเจกต์ที่มีอยู่ อาจส่งผลกระทบต่อประสิทธิภาพ ความซับซ้อนของ allocator หรือ garbage collector โดยทั่วไปขึ้นอยู่กับจำนวนออบเจกต์ใน heap
- โครงสร้างข้อมูลแบบวนซ้ำ เช่น กราฟ สร้างได้ยาก หากคุณมีออบเจกต์สองชิ้นที่ไม่สามารถแก้ไขได้หลังจากเริ่มต้นใช้งาน คุณจะทำให้พวกมันชี้ไปหากันได้อย่างไร?
คุณจะทำให้โค้ดของคุณเป็น immutable ได้อย่างไร?
อีกทางเลือกหนึ่งคือการใช้การประกาศ const ร่วมกับเทคนิคที่กล่าวมาข้างต้นสำหรับการสร้าง สำหรับออบเจกต์ที่ "mutating" ให้ใช้ spread operator, Object.assign, Array.concat() เป็นต้น เพื่อสร้างออบเจกต์ใหม่แทนที่จะเปลี่ยนแปลงออบเจกต์ต้นฉบับ
ตัวอย่าง:
// Array Example
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Object Example
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}