What are objects?

Just like other primitive values, objects are values too and they serve the same purpose: Representing information or data. When we create a variable and assign a value to it, we are holding information in it for later retrieval and processing. For example —

const firstName = 'Arfat Salman';

In this case, firstName holds and represents the first name of a person. In a sense, strings are called primitive values because they can represent only one type of information. In this case, a collection of characters or a string.

What happens when we want to represent a person as a whole? That is, I want to hold on to first name (String), last name (String), date of birth (Date), and her friends (Collection of Strings). If I create four variables for each datum, representing three persons would require 4 * 3 = 12 variable names. That’s not good!

In some languages, this question would segue into a discussion about user-defined data types or custom data types. Constructs such as classes, structures, unions types etc. can be used to achieve that. However, JavaScript does not have classes (even though it has a class keyword), or structures. It only has objects for creating custom data types and associating behavior.

In JavaScript, an object can be thought of as a distinct entity that can have properties associated with it. These properties define the object’s characteristics and behavior. In our person example, an object can have 4 properties describing it — first name, last name, DOB, and friends.

Here’s an example representation of “person” object.

Here’s how ECMA specification defines an object —

An Object is logically a collection of properties.

The properties are also known as key-value pairs. You may come across similar concepts in the form of Associative Arrays, maps, and dictionaries in other languages. JavaScript objects embody those characteristics, but they also have a lot more to them as we shall see.

Objects and Properties

We are going to examine objects by the kind of operation we can perform on them. The operations are —

Creation — Creating a new object.

— Creating a new object. Addition — Adding properties to an object.

— Adding properties to an object. Reading/Retrieving — Reading the value associated with a key.

— Reading the value associated with a key. Existence — Whether a property exists in an object.

— Whether a property exists in an object. Updation — Updating the values of existing properties.

— Updating the values of existing properties. Deletion — Deleting existing properties.

— Deleting existing properties. Iteration (Enumeration) — Looping over an object’s keys and values.

— Looping over an object’s keys and values. Comparison — Comparing two different objects.

— Comparing two different objects. Copying — Making a copy of an existing object

Creation

Properties of an object are just like variables. Let’s see how can we create objects and properties —

Creating an object using “literal” notation.

A literal is a notation for representing a fixed value in source code. For example, 5 or "foo" . They literally represent the value. An object is represented as —

const obj = { }; // Creating an object using literal notation

The curly braces { } create a new object.

You can also specify properties to be inserted at the time of creation.

const obj = {

firstName: 'Alex',

'lastName': 'Martin', // Using string quotes

dateOfBirth: '18th October',

friends: [ 'Bob', 'Christine' ]

};

The property names are treated as strings. That is, firstName and 'firstName' are same. An object property name can be any valid JavaScript string, or anything that can be converted to a string, including the empty string.

Note that the values can be any value type. That is, both primitive types and an object can be used as values.

const obj = {

nestedObj: {

name: 'Alex'

}

}

The nestedObj is a key that holds another object. The nesting can be arbitrary and cyclic too!

Creating objects using Object constructor

const obj = new Object();

This effect of this code and the literal notation is the same. However, it is advised not to use this pattern. You can read more about it here.

You can also use an expression that will be evaluated to a string using the new computed property syntax. As an example —

const propertyName = 'firstName';

const obj = {

[propertyName.toUpperCase()]: 'Alex',

} // { FIRSTNAME: 'Alex' }

Note the variable propertyName . In obj , we wrap the expression propertyName.toUpperCase() in [ ] to make it a computed property. As expected, in the resulting output, the string firstName is turned into uppercase.

Function as key values

In an object, when we have a function as a value to a key, then the function is called a method. This is one of the most important features of objects. The methods have access to the key-value pairs of the object. They can specify behavior in an object.

const obj1 = {

firstName: 'ALEX',

lastName: 'MARTIN',

printFullName() {

return `${this.firstName} ${this.lastName}`

}

}; obj1.printFullName();

//=> 'ALEX MARTIN'

We add printFullName method on obj1 . The method has access to the properties of the object using the this keyword. We return the full name formatted using template literals. In this case, the behavior is to print the full name. You can read more about methods here.

Addition

Via Giphy

After we have created our object, we can add properties to it. There are two distinct ways of adding properties —

Dot Notation

obj.address = 'Earth';

The object and the property name is separated by a . (dot). If a property with the same name exists, its value will be overwritten. Otherwise, a new property with property name address will be added to the object.

An object can only contain a single key with one value. We can’t have a single key having two different values.

Caveat: Using the dot-notation, you can’t use property names that are numbers, or string that contain spaces, or special character, or dynamic properties. For example, these will throw a SyntaxError .

obj.5 = 'five'; // Using numbers as property names

obj.first name = 'alex' // Using spaces between property names

obj.first-name = 'alex' // Using special characters -

Bracket Notation

If you don’t want to be limited by the above caveats, you may consider using bracket notation.

obj["address"] = 'Earth' // Note the quotes

Here we are adding address property to the obj object.

A few things to note —

The part between the brackets is an expression. That means we can use variables there. See the example below.

The type of properties can be either a string or a symbol . If the property is an expression, it will be evaluated. If the property is of any other type, it will be coerced to a string value.

const propName = 'address';

obj[propName] = 'Earth' // No string quotes

Executing obj.propName adds a property propName whereas obj[propName] adds a property after evaluating the variable propName . The content of propName is address , so a property address with the value Earth is added to obj .

Also, these are now valid —

obj[5] = 'five'; // 5 is coerced to a string to make '5'

obj['first name'] = 'alex'; // property as string

obj['first-name'] = 'alex'; // property as string

Reading/Retrieving

Retrieving a property is by far the most complex to understand in JavaScript. Let’s see some simple examples —

obj.address // => 'Earth'

obj['firstName'] // => 'Alex'

obj['middleName'] // => undefined

Like addition, we can use the dot-notation or bracket notation get the value of a property. If the property exists, we get its value. If the property does not exist, we get undefined . Simple!

There is always one! (via Giphy)

A Primer on Prototypes

Objects are not just containers of key-value pairs. They have one more very interesting property. They can have a parent. That is, they store a link to another object. This parent object is also consulted when a property is read.

And not just that. That parent can also have a parent. And that grandparent object is consulted too when a property is read. This repeats until we reach an object that does not have any parent i.e the parent is null .

Here’s the rough algorithm used when we retrieve a property —

Goal: Read middleName from the object obj . (Note that middleName does not exist in obj )

(Rough) Algorithm —

Let CurrentObject = obj . Let CurrentKeyName = middleName .

= . Let = . If CurrentObject has CurrentKeyName , return associated value.

has , return associated value. Else, let CurrentObject = parent of CurrentObject .

= parent of . Check for CurrentKeyName again in CurrentObject. If found, return value.

again in If found, return value. If CurrentObject has no parent and does not have CurrentKeyName, return undefined .

In the last step, CurrentObject would contain the final parent-less object. By this time, we have checked every object in the chain and none of them have the property that we are looking for. If the property is found at any level, its value is returned.

This chain is called the “prototype” chain. By default, all objects have their parent as Object.prototype . There is no parent to Object.prototype . That’s where the chain ends, normally. Here’s a representation of it —

Prototype chain

The [[Prototype]] property keeps this reference to the parent object. It’s an internal property which means that we can’t access it directly. We can use Object.getPrototypeOf function to get the parent (aka “prototype”) of an object.

An important point to note is that the prototype chain is consulted only while reading a value. It does not affect addition, updating or deletion of properties on parent objects. One way to look at this is — You have read-only access to all things your parent (and their parents) have.

Undefined

If the property does not exist in an object(or its parents), we get undefined . What happens when we intentionally set a property’s value to undefined ?

obj.firstName = undefined;

In this case, we have the property but its value is purposely not defined. When we retrieve the value, we’ll get undefined . But how can we be sure that its value in intentionally undefined or the property does not exist on the object?

To ascertain that we need to show the existence of a property. Keep reading!

Existence

Even with undefined. (via Giphy)

Sometimes we don’t care what value a property has. We only want to know whether the given object has the given property or not. There are 2 ways to know that —

in operator

'firstName' in obj; // => true

'middleName' in obj; // => false

'isPrototypeOf' in obj; // => true

The syntax is String (or Symbol) in object. It will evaluate to true or false depending on whether the property exists.

Attentive readers may be surprised to see the last example ( 'isPrototypeOf' in obj ) evaluate to true . This is the gotcha of in operator.

isPrototypeOf is a property of the parent of obj . Just like Reading a property, the entire prototype chain is consulted before true or false is returned. If the property is found at any level, true is returned.

What if we want our own properties? Properties that exist only on the current object in question irrespective of whether that same property exists on parents or not.

hasOwnProperty

It’s a function that can be accessed through an object because of prototypes. It takes string key name ( or symbol) as an argument.

obj.hasOwnProperty('firstName'); // => true

obj.hasOwnProperty('middleName'); // => false

obj.hasOwnProperty('isPrototypeOf'); // => false

This gives more predictable results. However, it’s not without its own set of gotchas. Read about them here.

Updation

Updating is simple. It’s just like addition. You can use dot-notation or bracket-notation. Here’s an example —

obj.firstName = 'Timothy'

obj['firstName'] = 'Timothy'

It has the side effect of creating a new property firstName and setting the value Timothy if the property did not exist. If you don’t want that, you should pair your code with existence checks.

Also, updating a property always affects the object on which it is performed. It does not modify any object in the prototype chain.

Deletion

Deletion is performed using the delete operator. Again, we can use both the notations.

delete obj.firstName; // => true

delete obj['firstName']; // => true

The return value of delete operator is true if the property was successfully deleted. Else, it will be false . We can make properties un-deletable. So, it is important to check the output of delete .

Also, merely setting a property’s value to undefined won’t delete the said property.

Iteration (Enumeration)

Mandelbrot set (via Giphy)

An object is a collection of key-value pairs. Often times you want to process the set of all values or keys and create another object with transformed values or key names. Unlike arrays, you can’t simply iterate an object. Here are a few ways to iterate objects —

for-in loops

for-in loop is just like a normal for loop except that it iterates on an object. You get the properties one by one. Here’s an example using obj object —

for (const property in obj) {

const value = obj[property]; // Read the value

console.log(property, value);

} // firstName Alex

// lastName Martin

// dateOfBirth 18th October

// friends [ 'Bob', 'Christine' ]

Note —

The order of appearance of properties is not fixed in an object.

for-in loop will return all string properties. It won’t give Symbol properties.

loop will return all properties. It won’t give Symbol properties. The prototype chain is consulted, and all enumerable properties of parents are also returned. (In short, enumerable properties are those properties which are visible to looping constructs.)

Because the prototype chain is consulted, we may get unwanted properties in a for-in loop. We can put an existence check using hasOwnProperty to only get own properties, but we have better ways to do the same thing now.

Object.keys()

Object.keys() is similar to for-in loop except that it only returns the object’s own keys in an array. That is, the prototype chain is not consulted. We can then iterate on this array using a for-of or a for loop. Here’s an example —

const allProperties = Object.keys(obj); // => [ 'firstName', 'lastName', 'dateOfBirth', 'friends' ]

for (const property of allProperties) {

const value = obj[property];

console.log(property, value);

}

Attentive readers will see that we have to manually read the value inside the loop using const value = obj[property]; . What if we only want the values?

Object.values()

Object.values() has similar restrictions as Object.keys() , but it returns an array of values instead of the keys.

const allValues = Object.values(obj); // => [ 'Alex', 'Martin', '18th October', [ 'Bob', 'Christine' ] ]

What if we want both values and properties at the same time?

Object.entries()

Same as .keys() and .values() except that we get an array of [key, value] pairs.

const allEntries = Object.entries(obj); // => Output [ [ 'firstName', 'Alex' ],

[ 'lastName', 'Martin' ],

[ 'dateOfBirth', '18th October' ],

[ 'friends', [ 'Bob', 'Christine' ] ] ]

You can use array “destructuring” and for-of loop to iterate entries very cleanly.

for (const [key, value] of Object.entries(obj)) {

console.log(key, value);

}

One restriction the above methods pose is that they only return string properties and not symbols in an object. What if we want both?

Reflect.ownKeys()

ownKeys return both string-based and symbol properties. Note that we are using Reflect module, and not Object module.

Comparison

There are no good in-built of ways of comparing two objects. When you use the == or === operators, they only compare the references of the objects. References can be understood as “memory addresses” of the objects. Only primitives types are compared by values.

Now, two objects can have the same key-values pairs but they can’t occupy the same location in memory or reference. The object is only equal to itself as the example shows us —

const obj1 = {

name: 'Alex',

} const obj2 = {

name: 'Alex',

} obj1 === obj2 // => false

obj1 === obj1 // => true

See exercises below for a solution to this.

Copying

This is another hard aspect of objects. When we use the = operator on an object, we merely copy its reference. Let’s see this by an example —

const obj1 = {

name: 'Alex',

} const obj2 = obj1;

We create an object obj1 . When we do obj2 = obj1 , obj2 does not get another new object with its own key-value pairs. Rather, it starts pointing to the object pointed by obj1 . This is the core difference between shallow copy and a deep copy. In deep copy, you would get another object with its own set of key-value pairs (having the same value as original ones).

Shallow vs Deep Copy

There are multiple ways to do a deep copy —

Object Spread Operator

We can use ... operator to a top-level deep copy. Here’s an example —

const obj1 = {

name: 'Alex',

nestedObj: {

address: 'Earth'

}

} const obj2 = {

...obj1 // Spreading the properties of obj1

}

However, this is only effective for the top-level primitive properties. In this case, obj2 gets its own name property. However, nestedObj is still copied shallowly.

Also, if the value of a property is a function then we can’t create a copy of function reliably in JavaScript. Hence, the functions/methods have to be shared.

JSON.stringify

We take a JSON representation of an object and parse it again to create another object with the same key-value pairs. This has the nice property that nested objects are also deeply copied.

const obj2 = JSON.parse(JSON.stringify(obj));

One drawback is that keys whose values are undefined , or functions or a Symbol are skipped. It has many more rules that you can read here.

Object.assign

The assign function takes a source and a target object. It copies all the enumerable, own key-value pairs in the target from the source.

const obj2 = Object.assign({}, obj1);

In this case, the source is obj1 and the target is an empty object { } .

Just like object spread operator, assign does not copy deeply on all levels and both String and Symbol properties are copied.

What about arrays?

Arrays are objects too. The only difference is that the keys are predetermined to be whole numbers (+0 ≤ i < 2³²–1). They are 0 , 1 , 2 … etc. by default. Also, while creating an array, the keys are automatically set. If we check using typeof [] , we will get object .

const arr = ['Alex', 'Earth']; typeof arr; // => object Object.entries(arr);

// [ [ '0', 'Alex' ], [ '1', 'Earth' ] ]

Arrays have length property denoting the number of items in the array that is managed by JavaScript. From the example, we can see that Object.entries return the key-value pairs of the arr object.

And functions?

They are objects too. Specifically, they are objects with internal (not accessible to us) [[Call]] method. Let’s see —

function func1() {

return 'Alex';

} typeof func1; // => 'function' func1.name; // => 'func1' func1.address = 'Earth'; // Setting our own property func1.address; // => 'Earth'

func1['address']; // => 'Earth'

We declare func1 as a function. If we do typeof func1 , we get function instead of object . However, that is because the ECMA Specification says so.

All functions have certain properties on them by default. name is one of them which stores the name of the function. In this case, name gives us func1 which was the original name of the declared function.

We can also add our own properties. In the example, address was added to func1 and later read like a normal property.

Conclusion

We have discussed quite a few things about objects. However, we have barely scratched the surface. There is another layer to JavaScript objects that we haven’t discussed. Hopefully, we’ll see that in a different article. In the meantime, here are a few exercises to cement your understanding of how objects work.

Exercises

Given an object, make a copy of the object with all key names capitalized.

const obj = { name: 'Alex' };

const newObj = capitalize(obj); // => { NAME: 'Alex' }

Create an equivalent of Array.map and Array.filter for objects. The callback will receive both key and value. See example below —

const obj1 = {

firstName: 'ALEX',

lastName: 'MARTIN'

}; const newObj = objectMap(obj1, (key, value) => {

return [

key.toUpperCase(),

value.toLowerCase(),

];

}); // => { FIRSTNAME: 'alex', LASTNAME: 'martin' }

What happens when the keys are Symbol s?

Write a function to deep compare two objects by value. What happens when the object’s values are functions? What about symbols?

Try not to use regular for loops for the exercises.