Object.freeze() and Object.seal() in JavaScript
Previously, we wrote about How to Create Immutable Objects in JavaScript. In this howto, we'll explore the difference between Object.freeze() and Object.seal().
Both Object.freeze() and Object.seal() are methods used to control how objects behave when modified, but they have distinct differences. Here's how each works:
Object.freeze()
The Object.freeze() method "freezes" an object, preventing any changes to its properties. This means:
- No new properties can be added to the object.
- Existing properties cannot be removed.
- Modifications to existing properties (like changing values) are not allowed.
- Reconfiguring property descriptors (like changing from writable to non-writable) is not allowed.
- The object becomes immutable, but note that for nested objects, only the outer object is frozen, not the deeper properties.
Example:
1 const person = { name: 'Alice', age: 30 };23 // Freeze the object4 Object.freeze(person);56 // Trying to modify properties7 person.age = 25; // This will not work8 person.city = 'New York'; // Adding new property will also fail910 console.log(person); // Output: { name: "Alice", age: 30 }
Key Points:
- It makes the object completely immutable.
- No changes to the object's structure or values are allowed.
As you can see, all attempts to modify the object failed. On a low level, Object.freeze() adds a non-configurable flag to all the object’s properties, preventing them from being altered.
However, if there are nested objects, Object.freeze() does not automatically freeze them. This means that the properties of nested objects can still be modified:
1 const person = {2 name: 'Alice',3 age: 30,4 address: {5 city: 'New York',6 },7 };89 // Freeze the object10 Object.freeze(person);1112 // Trying to modify properties13 person.address.city = 'Los Angeles'; // This will work
To freeze nested objects, you’ll need to do it manually or write a recursive function. DeepFreeze a nested Object/Array
Object.seal()
The Object.seal() method "seals" an object, restricting certain modifications but still allowing others:
- No new properties can be added.
- Existing properties cannot be removed.
- Modifying existing properties (like changing their values) is allowed.
- Property descriptors (like configurable) are set to false, meaning properties can't be redefined or removed.
- The object’s structure is sealed, but values can still be updated.
Example:
1 const car = { make: 'Chevrolet', model: 'Spark' };23 // Seal the object4 Object.seal(car);56 // Trying to add a new property7 car.year = 2022; // This will not work89 // Modifying an existing property10 car.model = 'Camaro'; // This will work1112 console.log(car); // Output: { make: "Toyota", model: "Camaro" }
Key Points:
- Sealed objects can have their existing properties modified.
- New properties cannot be added, and existing properties cannot be deleted.
Usage Examples
Protecting Configuration Objects
Configuration objects define the behavior of your application. They need to remain stable and unchangeable to avoid accidental errors:
1 const config = {2 apiUrl: 'https://api-domain.com',3 timeout: 4000,4 retries: 2,5 };67 Object.freeze(config);89 // Function to Retrieve Configuration10 function getConfig() {11 return config;12 }1314 // Usage Example15 console.log(getConfig().apiUrl); // "https://api-domain.com"1617 // Attempt to Modify Configuration18 config.apiUrl = 'https://new-api-domain.com'; // This will not work19 console.log(getConfig().apiUrl); // Still "https://api-domain.com"
By using Object.freeze(), we ensure that any attempts to modify config will be ignored.
Usage in Redux Library
In Redux, immutability of the state is key to predictability and easier debugging. By using Object.freeze(), you can protect the state from unwanted mutations.
Example:
1 const initialState = {2 user: null,3 loggedIn: false,4 };56 function reducer(state = initialState, action) {7 switch (action.type) {8 case 'LOGIN':9 return Object.freeze({10 ...state,11 user: action.payload,12 loggedIn: true,13 });14 case 'LOGOUT':15 return Object.freeze(initialState);16 default:17 return state;18 }19 }
Here, we use Object.freeze() to ensure that each time the state is updated, it remains unchanged.
Example with React
In React, managing component state is often necessary. Protecting state using Object.freeze() can help prevent errors caused by data mutations.
Example:
1 import React, { useState } from 'react';23 const App = () => {4 const [config, setConfig] = useState(5 Object.freeze({6 theme: 'light',7 notificationsEnabled: true,8 }),9 );1011 const toggleTheme = () => {12 // Создаем новый объект вместо изменения существующего13 setConfig((prevConfig) =>14 Object.freeze({15 ...prevConfig,16 theme: prevConfig.theme === 'light' ? 'dark' : 'light',17 }),18 );19 };2021 return (22 <div style={{ background: config.theme === 'light' ? '#fff' : '#333' }}>23 <h1>Текущая тема: {config.theme}</h1>24 <button onClick={toggleTheme}>Сменить тему</button>25 </div>26 );27 };2829 export default App;
In this example, we use Object.freeze() to protect the configuration state.
Protecting Constants and Global Variables
When working with constants, you may need to ensure that these values are not accidentally modified. With Object.freeze(), you can make constants truly immutable.
Example:
1 const constants = Object.freeze({2 MAX_CONNECTIONS: 150,3 DEFAULT_TIMEOUT: 4000,4 APP_NAME: 'MyApp',5 });67 // Attempting to modify a constant8 constants.MAX_CONNECTIONS = 100; // Will not work910 console.log(constants.MAX_CONNECTIONS); // 150
In this example, even if someone tries to change MAX_CONNECTIONS, the modification will not occur, and your application will remain stable.
Summary of Differences
- Object.freeze() makes an object completely immutable, preventing any changes, including modifications to values.
- Object.seal() allows modifying existing properties but prevents adding or removing properties.
These methods are useful for locking down objects to prevent accidental or unwanted changes.