Tính bất biến là một nguyên tắc cốt lõi trong lập trình hàm và cũng có nhiều điều để cung cấp cho các chương trình hướng đối tượng. Một đối tượng có thể thay đổi là một đối tượng mà trạng thái của nó có thể được sửa đổi sau khi nó được tạo. Một đối tượng bất biến là một đối tượng mà trạng thái của nó không thể được sửa đổi sau khi nó được tạo.
Ví dụ về đối tượng bất biến trong JavaScript?
Trong JavaScript, một số kiểu dựng sẵn (số, chuỗi) là bất biến, nhưng các đối tượng tùy chỉnh thường có thể thay đổi.
Một số đối tượng JavaScript bất biến được dựng sẵn là Math, Date.
Dưới đây là một vài cách để thêm/mô phỏng tính bất biến trên các đối tượng JavaScript thuần túy.
Thuộc tính hằng của đối tượng
Bằng cách kết hợp writable: false và configurable: false, bạn có thể tạo một hằng số (không thể thay đổi, định nghĩa lại hoặc xóa) làm thuộc tính đối tượng, như:
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
Ngăn chặn mở rộng
Nếu bạn muốn ngăn một đối tượng thêm các thuộc tính mới vào nó, nhưng vẫn giữ nguyên các thuộc tính khác của đối tượng, hãy gọi Object.preventExtensions(...):
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
Ở chế độ không nghiêm ngặt, việc tạo b thất bại một cách âm thầm. Ở chế độ nghiêm ngặt, nó ném ra một TypeError.
Seal
Object.seal() tạo một đối tượng đã niêm phong, có nghĩa là nó lấy một đối tượng hiện có và về cơ bản gọi Object.preventExtensions() trên nó, nhưng cũng đánh dấu tất cả các thuộc tính hiện có của nó là configurable: false.
Vì vậy, bạn không chỉ không thể thêm bất kỳ thuộc tính nào nữa mà bạn cũng không thể định cấu hình lại hoặc xóa bất kỳ thuộc tính hiện có nào (mặc dù bạn vẫn có thể sửa đổi giá trị của chúng).
Freeze
Object.freeze() tạo một đối tượng đóng băng, có nghĩa là nó lấy một đối tượng hiện có và về cơ bản gọi Object.seal() trên nó, nhưng nó cũng đánh dấu tất cả các thuộc tính trình truy cập dữ liệu là writable:false, để giá trị của chúng không thể bị thay đổi.
Cách tiếp cận này là mức độ bất biến cao nhất mà bạn có thể đạt được cho chính một đối tượng, vì nó ngăn chặn mọi thay đổi đối với đối tượng hoặc bất kỳ thuộc tính trực tiếp nào của nó (mặc dù, như đã đề cập ở trên, nội dung của bất kỳ đối tượng nào được tham chiếu khác đều không bị ảnh hưởng).
var immutable = Object.freeze({});
Đóng băng một đối tượng không cho phép thêm các thuộc tính mới vào một đối tượng và ngăn chặn việc xóa hoặc thay đổi các thuộc tính hiện có. Object.freeze() bảo toàn tính liệt kê, khả năng cấu hình, khả năng ghi và nguyên mẫu của đối tượng. Nó trả về đối tượng đã truyền và không tạo một bản sao đóng băng.
Ưu và nhược điểm của tính bất biến là gì?
Ưu điểm
- Phát hiện thay đổi dễ dàng hơn - Sự bằng nhau của đối tượng có thể được xác định một cách hiệu quả và dễ dàng thông qua sự bằng nhau tham chiếu. Điều này hữu ích để so sánh sự khác biệt của đối tượng trong React và Redux.
- Các chương trình với các đối tượng bất biến ít phức tạp hơn để suy nghĩ, vì bạn không cần phải lo lắng về cách một đối tượng có thể phát triển theo thời gian.
- Các bản sao phòng thủ không còn cần thiết khi các đối tượng bất biến được trả về từ hoặc truyền vào các hàm, vì không có khả năng một đối tượng bất biến sẽ bị sửa đổi bởi nó.
- Chia sẻ dễ dàng thông qua tham chiếu - Một bản sao của một đối tượng cũng tốt như một bản sao khác, vì vậy bạn có thể lưu trữ các đối tượng hoặc sử dụng lại cùng một đối tượng nhiều lần.
- An toàn luồng - Các đối tượng bất biến có thể được sử dụng an toàn giữa các luồng trong môi trường đa luồng vì không có rủi ro chúng bị sửa đổi trong các luồng khác đang chạy đồng thời.
- Sử dụng các thư viện như ImmutableJS, các đối tượng được sửa đổi bằng cách chia sẻ cấu trúc và cần ít bộ nhớ hơn để có nhiều đối tượng có cấu trúc tương tự.
Nhược điểm
- Các triển khai ngây thơ của cấu trúc dữ liệu bất biến và các hoạt động của nó có thể dẫn đến hiệu suất cực kỳ kém vì các đối tượng mới được tạo mỗi lần. Khuyến nghị sử dụng thư viện cho cấu trúc dữ liệu bất biến hiệu quả và các hoạt động tận dụng chia sẻ cấu trúc.
- Phân bổ (và giải phóng) nhiều đối tượng nhỏ thay vì sửa đổi các đối tượng hiện có có thể gây ảnh hưởng đến hiệu suất. Độ phức tạp của bộ cấp phát hoặc bộ thu gom rác thường phụ thuộc vào số lượng đối tượng trên heap.
- Các cấu trúc dữ liệu tuần hoàn như đồ thị rất khó xây dựng. Nếu bạn có hai đối tượng không thể sửa đổi sau khi khởi tạo, làm thế nào bạn có thể khiến chúng trỏ vào nhau?
Làm thế nào bạn có thể đạt được tính bất biến trong mã của riêng bạn?
Cách khác là sử dụng khai báo const kết hợp với các kỹ thuật đã đề cập ở trên để tạo. Đối với các đối tượng có thể thay đổi, hãy sử dụng toán tử spread, Object.assign, Array.concat(), v.v., để tạo các đối tượng mới thay vì thay đổi đối tượng gốc.
Ví dụ:
// Ví dụ Mảng
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Ví dụ Đối tượng
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}