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)
false
Description 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)
true
Object.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$/);