Understanding JavaScript Objects
Objects are a core concept in JavaScript. When I started learning about Objects it seemed to me pretty straightforward, just pairs of keys and values they said.
It took me some time to realize that this topic is much more complicated than I had in mind. I then started learning from different sources, some were good, but I could never get “the big picture”.
In this post I have tried to create a comprehensive overview of objects in JS, not going too deep into any subject, but just deep enough to help you understand and feel comfortable to go and read further.
So, let’s start from the beginning.
Object
A JavaScript object is just a collection of properties, each property is a pair of a key and a value.
You can access keys using dot notation( obj.a
) or brackets notation ( obj['a']
). Remember that you should use the brackets notation if your key is one of the following:
1. Not a valid JavaScript identifier (has space, dash, starts with a number...)
2. Is a variable
Objects in JS are created with a property called Prototype
which is a very important subject to master.
prototype:
Every object in JavaScript has an internal property called Prototype
, in most browsers you can access it as _proto_
. Prototype
is Javascript’s solution for inheritance, which allows sharing functionality without duplicating the code in memory. It does so by linking one object to another.
Simply put, Prototype
creates a reference from one object to another.
Prototype chain — Every time you look for a property in an object, JS will try to find it on the object, if it can’t, it will look inside the Prototype
object. If the property is still missing, JS will go on looking in the Prototype
of the linked object. This will continue until JS finds the relevant property or until the end of the prototype chain.
Let’s take an example:
cons
is a constructor (just a function that can use the new
operator). At line 5 we create new object, a new instance of cons
. when created, obj
also gets a prototype property.
Now we add properties (‘b’, ‘c’)
to the prototype
object of cons
and let’s take a look at obj
:
obj.a // 1
— nothing new here, obj.a is still 1.obj.c
— there is no c
property on obj
! but just as we said earlier, JS will now look in obj
Prototype
and will come back with a 4.
Now ask yourself what is the value of obj.b
and what would be the value after we delete obj.b
?
obj.b
is 2 . We did set a b
property but on cons
Prototype
so when we check for obj.b
we still get 2. However, after we delete obj.b
, JS can’t find b
on obj
anymore so it goes to its prototype
and come back with a 3.
I want to briefly show the different ways to create an object and talk some more about prototype.
creating an object
Object literal: var obj = {a: 1};
we have created an object with the following ptototype chain: obj ---> Object.prototype ---> null
As you can guess object.prototype
is the prototype of object and it is also the end of the prototype chain.
Object.create(): var newObj = Object.create(obj);
newObj will have this ptototype chain: newObj ---> obj ---> Object.prototype ---> null
Constructor: As the example above, a constructor is just a JS function that allows us to use new
operator to create instances of it.
ES6 Classes:
class rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
getArea() {
return this.height * this.width;
}
}
let square = new rectangle(2, 2);
Square
is an instance of rectangle
constructor, so we can call square.getArea() //4
, square.width
and also all the functions it inherited from object.prototype
.
Which way should you choose? If you plan to create several instances you better use ES6 / constructor method but if you plan to create a one time object you might want to choose object literal because it is probably the easiest.
Now that we know about prototype
and are familiar with all the ways to create new objects we can move forward talking about one of the most confusing parts in objects:
Comparing & changing Objects
In JavaScript objects are a reference type.
When we create an object var obj = {a: 1};
the variable obj
receives the location in memory of the object and not the value! This is extremely important to understand because it can cause a variety of errors. When we create another object var newObj = obj
, we have actually created a pointer to the location in memory of the obj
and not a totally new object.
This means that when we do newObj.a = 2
we actually change obj
so obj.a
is now 2 !
This is so bug prone that many companies are working with immutable objects, meaning you can not change the object you have created. Instead you have to create a new object (a copy of the original one) and change it. This is the way important libraries like Redux are working and it is one of the most important concepts in functional programming. You can read more about it here.
Equality: This also means that two objects are never equal, even if they have the exact same properties. This is because JS actually compares the location in memory of the objects and two objects can never be in the same memory cell.
// Two distinct objects with the same properties are not equal
var fruit = {name: 'apple'};
var fruitbear = {name: 'apple'};
fruit === fruitbear; // return false
// here fruit and fruitbear are pointing to same object
var fruit = {name: 'apple'};
var fruitbear = fruit;
fruit === fruitbear; // return true
So by now you might be thinking how can I compare objects or how should I manipulate objects immutably?
Let’s go over some possibilities:
Object changing:
We understand we better not mutate our objects, so we would like to create a copy of the relevant object and change its properties.
object.assign() to the rescue.
var obj = { a : 1, b : 2};
var newObj = Object.assign({}, obj,{a:2}) // {a : 2, b : 2 }
If we want to change a
property value of obj
we can use object.assign to create a copy of obj
and change it.
You can see how we first create an empty object, then copy the values of obj
and later add our changes so eventually we get a new updated object to use.
Please pay attention that this will not work for a deep copy . A deep copy is when we want to copy an object which has one object property or more:
const obj = {a : 1, b : { a : 1 } }; // b property is an object
Object.assign()
copies property values, so when the property value is a reference to an object, it only copies that reference.
For deep copy we have to copy recursively. We could create a function or simply use _.cloneDeep
of Lodash.
Object comparing :
One cool trick for working with objects is to stringify them.
What we do here is to stringify both objects, and then compare the resulted strings :
JSON.stringify(obj1) === JSON.stringify(obj2)
This works because we now compare strings which is a reference done by value data type. The bad news is that it is not always going to work, mainly because object properties order is not guaranteed.
Another good solution is to use Lodash’s _.isEqual
which is making a deep object comparison.
Before we finish, lets go over some object’s interview questions which will help us dive in some more and to practice what we have learned.
Try to think about an answer before going on reading the rest.
How to find out the length of an object?
To find the answer we would need to iterate over the object properties one by one and to count them. There are number of ways to iterate over an object:
- for in: This method traverses all enumerable properties of an object and its prototype chain. Now that we (hopefully) know prototype we can easily realize that if we want to get only the object properties it might not be right to use
for in
. - object.keys: This method returns an array with all the own (only on object) enumerable properties keys of an object. This is better because we are only working on the object properties without its
prototype
properties. Rarely you will set property’senumerable
attribute to false, causing object.keys to skip them and you might not get the result you were wishing for. This is wheregetOwnPropertyNames
can be handy. - getOwnPropertyNames returns an array containing all object own keys (enumerable or not).
Also worth mentioning:
- object.values iterate over own and enumerable properties and returns an array with the relevant values.
- object.entries iterate over own and enumerable properties and returns an array with pairs of keys and values
As you can see, most of the above methods return an array, so you can leverage all of the methods available to JavaScript arrays.
One of this methods is array.length
so we can simply write:
let objLength = Object.getOwnPropertyNames(obj).length;
How to check if an object is empty ?
JSON.stringify(myObj) === “{}”
— we are using the stringify tool again, which allows us to easily check if the object is empty (comparing strings not objects)!Object.keys(myobj).length // true
— As we said before, converting object’s keys to array can benefit us. Here we are taking advantage of the length property inherited fromArray.prototype
to check for the length of the keys array. In JS0
is converted to false so when we add not!
we turn it to true while all other number will result in false.
In Conclusion
We have been through a lot. I hope you now feel somewhat more comfortable creating and dealing with objects.
- Remember that objects are a reference type, which means you are advised to work immutably.
- Make friends with the prototype property and the prototype chain.
- Get familiar with the different tools that help us work with objects. Remember that you can stringify it, create an array of its keys, or just traverse through its properties with one of the various methods we have learned.
I wish you great luck exploring JavaScript objects. Have fun with it!
Feel free to connect with us: Halolabs.io | Twitter | LinkedIn