In this chapter, we’ll look at how objects can be protected from being changed. Examples include: Preventing properties being added and preventing properties being changed.
Required knowledge: property attributes
For this chapter, you should be familiar with property attributes. If you aren’t, check out §9 “Property attributes: an introduction”.
JavaScript has three levels of protecting objects:
Object.preventExtensions(obj)Object.seal(obj)Object.freeze(obj)This method works as follows:
obj is not an object, it returns it.obj so that we can’t add properties anymore and returns it.<T> expresses that the result has the same type as the parameter.Let’s use Object.preventExtensions() in an example:
const obj = { first: 'Jane' };
Object.preventExtensions(obj);
assert.throws(
() => obj.last = 'Doe',
/^TypeError: Cannot add property last, object is not extensible$/);We can still delete properties, though:
assert.deepEquals(
Object.keys(obj), ['first']);
delete obj.first;
assert.deepEquals(
Object.keys(obj), []);Checks whether obj is extensible – for example:
> const obj = {};
> Object.isExtensible(obj)
true
> Object.preventExtensions(obj)
{}
> Object.isExtensible(obj)
falseDescription of this method:
obj isn’t an object, it returns it.obj, makes all of its properties unconfigurable, and returns it. The properties being unconfigurable means that they can’t be changed, anymore (except for their values): Read-only properties stay read-only, enumerable properties stay enumerable, etc.The following example demonstrates that sealing makes the object non-extensible and its properties unconfigurable.
const obj = {
first: 'Jane',
last: 'Doe',
};
// Before sealing
assert.equal(Object.isExtensible(obj), true);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
first: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: true
},
last: {
value: 'Doe',
writable: true,
enumerable: true,
configurable: true
}
});
Object.seal(obj);
// After sealing
assert.equal(Object.isExtensible(obj), false);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
first: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: false
},
last: {
value: 'Doe',
writable: true,
enumerable: true,
configurable: false
}
});We can still change the the value of property .first:
But we can’t change its attributes:
assert.throws(
() => Object.defineProperty(obj, 'first', { enumerable: false }),
/^TypeError: Cannot redefine property: first$/);Checks whether obj is sealed – for example:
obj if it isn’t an object.obj, and returns it. That is, obj is not extensible, all properties are read-only and there is no way to change that.const point = { x: 17, y: -5 };
Object.freeze(point);
assert.throws(
() => point.x = 2,
/^TypeError: Cannot assign to read only property 'x'/);
assert.throws(
() => Object.defineProperty(point, 'x', {enumerable: false}),
/^TypeError: Cannot redefine property: x$/);
assert.throws(
() => point.z = 4,
/^TypeError: Cannot add property z, object is not extensible$/);Checks whether obj is frozen – for example:
> const point = { x: 17, y: -5 };
> Object.isFrozen(point)
false
> Object.freeze(point)
{ x: 17, y: -5 }
> Object.isFrozen(point)
trueObject.freeze(obj) only freezes obj and its properties. It does not freeze the values of those properties – for example:
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
Object.freeze(teacher);
// We can’t change own properties:
assert.throws(
() => teacher.name = 'Elizabeth Hoover',
/^TypeError: Cannot assign to read only property 'name'/);
// Alas, we can still change values of own properties:
teacher.students.push('Lisa');
assert.deepEqual(
teacher, {
name: 'Edna Krabappel',
students: ['Bart', 'Lisa'],
});If we want deep freezing, we need to implement it ourselves:
function deepFreeze(value) {
if (Array.isArray(value)) {
for (const element of value) {
deepFreeze(element);
}
Object.freeze(value);
} else if (typeof value === 'object' && value !== null) {
for (const v of Object.values(value)) {
deepFreeze(v);
}
Object.freeze(value);
} else {
// Nothing to do: primitive values are already immutable
}
return value;
}Revisiting the example from the previous section, we can check if deepFreeze() really freezes deeply:
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
deepFreeze(teacher);
assert.throws(
() => teacher.students.push('Lisa'),
/^TypeError: Cannot add property 1, object is not extensible$/);