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

🧠 Test Your Knowledge

What's the difference between Object.freeze() and Object.seal()?