Variable immutability with Object freeze, seal and preventExtensions methods

Oleh Baranovskyi
5 min readMar 6, 2018

Here we are going to cover the following topics:

  1. var, let and const keywords
  2. Object.defineProperty() & Object.defineProperties()
  3. Object.freeze(), Object.seal(), Object.preventExtensions()

var, let and cons keywords

First of all I wanna to remind that JavaScript not support block scopes. I will show you that in the following simple example:

var log = console.log;for (var index = 0; index < 1; index++) {
var secondIndex = 5;
}
log(index); // 1
log(secondIndex); // 5
if (true) {
var varInIf = 2;
}
console.log(varInIf); // 2

Of course there is ways to use block scope. First way is to use function scope and second way is to use try/catch block:

// with function
function setVar() {
var varInFn = 33;
}
setVar();
// log(varInFn); // "ReferenceError: varInFn is not defined
// with try/catch block
try {
throw 2
} catch (a) {
log(a); // 2
}
// log(a); // "ReferenceError: a is not defined

But still we want block-scoped variables in normal way! ES6 will help us with that with let and const keywords, so now we can live in block scopes:

for (let index = 0; index < 1; index++) {
let secondIndex = 5;
}
// log(index); // "ReferenceError: index is not defined
// log(secondIndex); // "ReferenceError: secondIndex is not defined
if (true) {
let varInIf = 2;
}
// console.log(varInIf); // "ReferenceError: varInIf is not defined

We can see that let keyword variables can have block scoped behavior and same is for const. But const keyword have a some extra abilities related with immutability and I will talk about it soon.

If you are using var keyword you can create n number of variables with the same name if you change var to let will be thrown a SyntaxError:

var a = 2;
var a = 3;
log(a); // 3// version with let
let a = 2;
let a = 3; // "SyntaxError: Identifier 'a' has already been declared

Keyword const help us to create constants (previously we didn’t had that ability) but it has pros and cons. Let’s firstly look on pros:

const a = 3;
log(a); // 3
a = 5; // "TypeError: Assignment to constant variable.

Here we can see that we can’t reassign a variable, so now we have a new great syntax sugar and even some new abilities. But do not rush to rejoice! It’s only for numbers, strings, boolean and references! Objects and Arrays are not immutable.

// const objects
const a = {innerVar: 3};
a.innerVar = 5;
log(a); // { innerVar: 5}
// const with arrays
const b= [1,2,3];
b[1] = 5;
log(b); // [1,5,3]

In order to make object fully immutable we should combine const and Object.freeze() function but before that let’s try to understand how are working property descriptors in Object.defineProperty() and Object.defineProperties() methods.

Object.defineProperty() & Object.defineProperties()

Object.defineProperty() defines a new property directly on an object, or just modify it.
Object.defineProperties() works same as Object.defineProperty() but with multiple properties.

What is important to know about those functions is that we can make a really flexible configurations using them. Ok, let’s cover configurations one by one:

value: will set the initial property value

let obj = {};
Object.defineProperty(obj, 'myProp', {value: 'Hello'});
log(obj.myProp); // Hello;

enumerable: gives ability to get property key in for...in loop and will be included in array returned by Object.keys function.

let enumerableObj = {};
Object.defineProperty(enumerableObj, 'myProp', {enumerable: true});
log(Object.keys(enumerableObj));// ['myProp']
let enumerableObj2 = {};
Object.defineProperty(enumerableObj2, 'myProp', {enumerable: false});
log(Object.keys(enumerableObj2));// []

writable: gives ability to you change value of variable after it was configured and created

let writableObj = {};
Object.defineProperty(writableObj, 'myProp', {writable: true, value: 'Hello'});
writableObj.myProp = 'Hi';
log(writableObj.myProp); // Hi
let writableObj2 = {};
Object.defineProperty(writableObj2, 'myProp', {writable: false, value: 'Hello'});
writableObj2.myProp = 'Hi';

configurable: gives ability to update property configurations using Object.defineProperty() function in the future:

let configurableObj = {};
Object.defineProperty(configurableObj, 'myProp', {value: 'prop val', configurable: true});
Object.defineProperty(configurableObj, 'myProp', {value: 'new val'});
log(configurableObj.myProp); // new val
let configurableObj2 = {};
Object.defineProperty(configurableObj2, 'myProp', {value: 'prop val', configurable: false});
// Object.defineProperty(configurableObj2, 'myProp', {value: 'new val'});
// here we will get -> TypeError: Cannot redefine property: myProp

set & get: are working as common accessors

let value
, obj = {};
Object.defineProperty(obj, 'myProp', {
get: () => value,
set: val => value = val
});
obj.myProp = 'Hello'log(obj.myProp); // Hello

Object.freeze()

There is several rules of Object.freeze() method, let’s break it down on pieces:

var user = Object.freeze({
name: 'Oleh',
age: 29,
address: {
city: 'Lviv'
}
});
  1. Object.freeze() prevents new properties from being added to it:
user.newProp = 123;
log(user.newProp); // undefined

2. Object.freeze() prevents existing properties from being removed:

delete user.age;
log(user.age);
log(Reflect.deleteProperty(user, 'age')); // false
// Reflect.deleteProperty return false in case if
// property was not removed

3. Object.freeze() prevents existing properties, or their enumerability, configurability, or writability, from being changed:

log(Object.getOwnPropertyDescriptors(user));
// all properties get the following property descriptors:
// [{... configurable: false, writable: false}, ...]

And of course we can check if object is frozen using Object.isFrozen() method

let obj = Object.freeze({});
log(Object.isFrozen(obj)); // true

But there is one important pitfall that should be discussed, Object.freeze() do not make deep freeze:

user.address.city = 'Other';
log(user.address.city); // Other

Let’s implement deepFreeze() function! The following implementation of deepFreeze function solves the problem.

function deepFreeze(object) {
Object.getOwnPropertyNames(object).forEach(name => {
var prop = object[name];

if (prop && typeof prop === 'object')
deepFreeze(prop);
});

return Object.freeze(object);
}

And now we can create fully immutable objects using combination of const and deepFreeze function:

const user = deepFreeze({
name: 'Oleh',
age: 29,
address: {
city: 'Lviv'
}
});

It’s always you choice what to use, but it’s nothing bad if I will share my opinion. I use the following rules:

  1. If needed to define normal variable I always use let keyword
  2. If needed to define constant and it’s type a string, number or boolean I use const
  3. If needed to define constant and it’s an object I use combination of const keyword and deefFreeze() function

Object.seal()

var user = Object.seal({
name: 'Oleh',
age: 29,
address: {
city: 'Lviv'
}
});
  1. Object.seal() preventing new properties from being added
user.newProp = 123;
log(user.newProp); // undefined

2. Object.seal() preventing properties from being deleted

delete user.age;
log(user.age);
log(Reflect.deleteProperty(user, 'age')); // false

3. Object.seal() marking all existing properties as non-configurable. Unlike Object.freeze() it’s writable

log(Object.getOwnPropertyDescriptors(user));
// [{... configurable: false, writable: true}, ...]
// so
user.age = 18;
log(user.age); // 18

Also you can use deepFreeze and change all ‘freeze’ to ‘seal’ and you will get function deepSeal if there is some need in such functionality.

We can check if object is sealed using Object.isSealed() method

let obj = Object.seal({});
log(Object.isSealed(obj)); // true

Object.preventExtensions()

Object.preventExtensions() method prevents new properties from ever being added to an object.

var obj = {};Object.preventExtensions(obj);obj.name = 'Oleh';
log(obj.name); // undefined

In order to check if object is extensible we can use Object.isExtensible() method

let obj = {}; 
Object.preventExtensions(obj);
log(Object.isExtensible(obj)); // false

Conclusion

Thank you guys for reading. I hope you enjoyed it and learned some new stuff related to JavaScript. Please subscribe and press ‘Clap’ button if you like this article.

--

--