Last week, I wrote about property descriptors in JavaScript. I mentioned that you can use the static Object.defineProperty() method to change the configuration of a property, e.g. whether it’s writable and/or configurable. Today, I’ll cover three further methods that help prevent changes to an object.

Object.preventExtensions()

The static Object.preventExtensions() method prevents the addition of new properties on an object. It also prevents the object’s prototype from being reassigned. However, existing properties can still be reassigned and deleted.

In this example, we can still reassign dog.name and we can still delete dog.bark. However, attempting to assign dog.breed (a new property) causes an error to be thrown in strict mode. Attempting to set the prototype of the object referenced by dog causes an error to be thrown regardless of strict mode.

let dog = Object.preventExtensions({
name: "Rex",
bark() {
return "Woof!";
},
});

dog.name = "Spot"; // can still change values

delete dog.bark; // can still delete properties

dog.breed = "Springer Spaniel"; // throws an error in strict mode

class Dog {
constructor(name) {
this.name = name;
}

bark() {
return "Woof!";
}
}

Object.setPrototypeOf(dog, Dog.prototype); // throws an error

You can use the accompanying method, Object.isExtensible(), to check if an object is extensible.

if (Object.isExtensible(dog)) {
// do something if the object referenced by `dog` is extensible...
}

Object.seal()

In addition to preventing extensions, the static Object.seal() method makes an object’s existing properties non-configurable. This means their configuration can’t be changed with the Object.defineProperty() method and they can’t be deleted. A writable property can still be made read-only, however.

In this example, dog.name is writable, so we can still make it read-only. Attempting to delete dog.bark causes an error to be thrown in strict mode. Attempting to change the configuration of dog.name causes an error to be thrown regardless of strict mode.

let dog = Object.seal({
name: "Rex",
bark() {
return "Woof!";
},
});

Object.defineProperty(dog, "name", {
writable: false,
}); // can still change from writable to read-only

delete dog.bark; // throws an error in strict mode

Object.defineProperty(dog, "name", {
enumerable: false,
}); // throws an error

You can use the accompanying method, Object.isSealed(), to check if an object is sealed.

if (Object.isSealed(dog)) {
// do something if the object referenced by `dog` is sealed...
}

Object.freeze()

In addition to preventing extensions and configuration, the static Object.freeze() method makes an object’s existing properties read-only. This is the strictest method of the three. However, it only performs a shallow freeze, so nested objects can still be changed. You would need to recursively call the Object.freeze() method to get a deep freeze.

In this example, we can still set a property on dog.bark because it’s assigned to an object of type Function. However, attempting to reassign dog.name causes an error to be thrown.

let dog = Object.freeze({
name: "Rex",
bark() {
return "Woof!";
},
});

dog.bark.foo = "bar"; // ok because it's only a shallow freeze

dog.name = "Spot"; // throws an error in strict mode

You can use the accompanying method, Object.isFrozen(), to check if an object is frozen.

if (Object.isFrozen(dog)) {
// do something if the object referenced by `dog` is frozen...
}

Summary

Next week, I’ll share a useful application of the Object.freeze() method. Until then, here’s a summary of what we covered today.

  • The Object.preventExtensions() method prevents the addition of new properties on an object.
  • The Object.seal() method also makes an object’s existing properties non-configurable and prevents their deletion.
  • The Object.freeze() method also makes an object’s existing properties read-only. However, it only performs a shallow freeze.