JavaScript Object Protection
Securing and controlling object modifications
🛡️ What is Object Protection?
Object protection involves controlling how objects can be modified. JavaScript provides several methods to prevent, restrict, or monitor changes to objects, helping you create more secure and predictable code.
// Protect an object from changes
let config = {apiKey: "secret123", version: "1.0"};
Object.freeze(config); // Now config cannot be modified
Object Protection Methods
Object.freeze()
Completely immutable object
Object.freeze(obj);
// No changes allowed
Object.seal()
Prevent adding/removing properties
Object.seal(obj);
// Can modify, not add/remove
Object.preventExtensions()
Prevent adding new properties
Object.preventExtensions(obj);
// Can modify/delete existing
Property Descriptors
Fine-grained property control
Object.defineProperty(obj, 'prop', {
writable: false
});
🔹 Object.freeze() - Complete Protection
Object.freeze() makes an object completely immutable:
let user = {
name: "John",
age: 30,
settings: {
theme: "dark",
notifications: true
}
};
console.log("Before freeze:");
console.log("Can modify?", !Object.isFrozen(user)); // true
// Freeze the object
Object.freeze(user);
console.log("After freeze:");
console.log("Is frozen?", Object.isFrozen(user)); // true
// Try to modify (will fail silently in non-strict mode)
user.name = "Jane"; // Ignored
user.email = "new@email"; // Ignored
delete user.age; // Ignored
console.log("Name after attempt:", user.name); // Still "John"
console.log("Has email?", "email" in user); // false
// Note: Freeze is shallow - nested objects are not frozen
user.settings.theme = "light"; // This works!
console.log("Theme changed:", user.settings.theme); // "light"
// For deep freeze, you need a custom function
function deepFreeze(obj) {
// Get property names
Object.getOwnPropertyNames(obj).forEach(function(prop) {
// Freeze nested objects
if (obj[prop] !== null && typeof obj[prop] === "object") {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
}
let deepFrozenUser = deepFreeze({
name: "Alice",
settings: {theme: "dark"}
});
deepFrozenUser.settings.theme = "light"; // Ignored
console.log("Deep frozen theme:", deepFrozenUser.settings.theme); // Still "dark"
Output:
Before freeze:
Can modify? true
After freeze:
Is frozen? true
Name after attempt: John
Has email? false
Theme changed: light
Deep frozen theme: dark
🔹 Object.seal() - Partial Protection
Object.seal() prevents adding or removing properties but allows modification:
let product = {
name: "Laptop",
price: 999,
category: "Electronics"
};
console.log("Before seal:");
console.log("Is sealed?", Object.isSealed(product)); // false
// Seal the object
Object.seal(product);
console.log("After seal:");
console.log("Is sealed?", Object.isSealed(product)); // true
// Can modify existing properties
product.price = 899;
console.log("New price:", product.price); // 899
// Cannot add new properties
product.brand = "Dell"; // Ignored
console.log("Has brand?", "brand" in product); // false
// Cannot delete properties
delete product.category; // Ignored
console.log("Still has category?", "category" in product); // true
// Check what operations are allowed
console.log("Can add properties?", Object.isExtensible(product)); // false
console.log("Is frozen?", Object.isFrozen(product)); // false (can still modify)
// Get property descriptor to see the changes
let descriptor = Object.getOwnPropertyDescriptor(product, "name");
console.log("Name descriptor:", descriptor);
// {value: "Laptop", writable: true, enumerable: true, configurable: false}
Output:
Before seal:
Is sealed? false
After seal:
Is sealed? true
New price: 899
Has brand? false
Still has category? true
Can add properties? false
Is frozen? false
Name descriptor: {value: "Laptop", writable: true, enumerable: true, configurable: false}
🔹 Object.preventExtensions() - Minimal Protection
Object.preventExtensions() only prevents adding new properties:
let settings = {
theme: "light",
language: "en"
};
console.log("Before preventExtensions:");
console.log("Is extensible?", Object.isExtensible(settings)); // true
// Prevent extensions
Object.preventExtensions(settings);
console.log("After preventExtensions:");
console.log("Is extensible?", Object.isExtensible(settings)); // false
// Can modify existing properties
settings.theme = "dark";
console.log("Theme changed to:", settings.theme); // "dark"
// Can delete existing properties
delete settings.language;
console.log("Language deleted?", !("language" in settings)); // true
// Cannot add new properties
settings.fontSize = "large"; // Ignored
console.log("Has fontSize?", "fontSize" in settings); // false
// Still not sealed or frozen
console.log("Is sealed?", Object.isSealed(settings)); // false
console.log("Is frozen?", Object.isFrozen(settings)); // false
// Practical example: Configuration object
let appConfig = {
apiUrl: "https://api.example.com",
timeout: 5000
};
Object.preventExtensions(appConfig);
// This is allowed - modifying existing config
appConfig.timeout = 10000;
// This is prevented - adding new config
appConfig.newFeature = true; // Ignored
console.log("Final config:", appConfig);
Output:
Before preventExtensions:
Is extensible? true
After preventExtensions:
Is extensible? false
Theme changed to: dark
Language deleted? true
Has fontSize? false
Is sealed? false
Is frozen? false
Final config: {apiUrl: "https://api.example.com", timeout: 10000}
🔹 Property Descriptors - Fine-grained Control
Control individual property behavior with descriptors:
let person = {};
// Define properties with specific descriptors
Object.defineProperty(person, 'name', {
value: 'John',
writable: true, // Can be changed
enumerable: true, // Shows in for...in loops
configurable: true // Can be deleted or reconfigured
});
Object.defineProperty(person, 'id', {
value: 12345,
writable: false, // Cannot be changed
enumerable: false, // Hidden from for...in loops
configurable: false // Cannot be deleted
});
Object.defineProperty(person, 'email', {
value: '[email protected]',
writable: true,
enumerable: true,
configurable: false // Cannot be deleted but can be modified
});
// Test the properties
console.log("Initial person:", person);
// Can modify name (writable: true)
person.name = "Jane";
console.log("Name changed:", person.name); // "Jane"
// Cannot modify id (writable: false)
person.id = 99999; // Ignored
console.log("ID unchanged:", person.id); // 12345
// Can modify email (writable: true)
person.email = "[email protected]";
console.log("Email changed:", person.email); // "[email protected]"
// Check enumerable properties
console.log("Enumerable keys:", Object.keys(person)); // ["name", "email"] (id is hidden)
// Try to delete properties
delete person.name; // Works (configurable: true)
delete person.id; // Ignored (configurable: false)
delete person.email; // Ignored (configurable: false)
console.log("After deletion attempts:", person);
// Define multiple properties at once
let car = {};
Object.defineProperties(car, {
make: {
value: 'Toyota',
writable: false,
enumerable: true
},
model: {
value: 'Camry',
writable: true,
enumerable: true
},
year: {
value: 2022,
writable: false,
enumerable: false
}
});
console.log("Car object:", car);
console.log("Car keys:", Object.keys(car)); // Only make and model
Output:
Initial person: {name: "John", email: "[email protected]"}
Name changed: Jane
ID unchanged: 12345
Email changed: [email protected]
Enumerable keys: ["name", "email"]
After deletion attempts: {id: 12345, email: "[email protected]"}
Car object: {make: "Toyota", model: "Camry"}
Car keys: ["make", "model"]
🔹 Practical Protection Examples
Real-world scenarios for object protection:
// Example 1: Immutable Configuration
function createConfig(options) {
let config = {
apiUrl: options.apiUrl || 'https://api.default.com',
timeout: options.timeout || 5000,
retries: options.retries || 3,
version: '1.0.0'
};
// Make configuration immutable
return Object.freeze(config);
}
let appConfig = createConfig({apiUrl: 'https://myapi.com'});
appConfig.timeout = 10000; // Ignored
console.log("Config timeout:", appConfig.timeout); // Still 5000
// Example 2: Protected User Data
function createUser(userData) {
let user = {};
// Public readable properties
Object.defineProperty(user, 'name', {
value: userData.name,
writable: false,
enumerable: true
});
Object.defineProperty(user, 'email', {
value: userData.email,
writable: false,
enumerable: true
});
// Private property (not enumerable)
Object.defineProperty(user, 'id', {
value: Math.random().toString(36),
writable: false,
enumerable: false
});
// Method to get private data
Object.defineProperty(user, 'getId', {
value: function() { return this.id; },
writable: false,
enumerable: false
});
return Object.seal(user); // Prevent adding new properties
}
let user = createUser({name: 'Alice', email: '[email protected]'});
console.log("User:", user); // {name: "Alice", email: "[email protected]"}
console.log("User ID:", user.getId()); // Random ID
console.log("Keys:", Object.keys(user)); // ["name", "email"]
// Example 3: Constants Object
let CONSTANTS = {};
Object.defineProperties(CONSTANTS, {
PI: {value: 3.14159, writable: false, enumerable: true},
MAX_USERS: {value: 1000, writable: false, enumerable: true},
API_VERSION: {value: 'v2', writable: false, enumerable: true}
});
Object.freeze(CONSTANTS);
console.log("Constants:", CONSTANTS);
CONSTANTS.PI = 3.14; // Ignored
console.log("PI unchanged:", CONSTANTS.PI); // Still 3.14159
Output:
Config timeout: 5000
User: {name: "Alice", email: "[email protected]"}
User ID: [random string]
Keys: ["name", "email"]
Constants: {PI: 3.14159, MAX_USERS: 1000, API_VERSION: "v2"}
PI unchanged: 3.14159