Objects in JavaScript in Depth

All the stuff about JavaScript objects in one single place!

By: Ajdin Imsirovic 21 October 2019

In this article, we’ll take a really detailed look into objects in JavaScript.

Everything about objects in javascript Image by CodingExercises

What are objects in JS?

In JavaScript, objects are simply collections of key-value pairs. These key-value pairs are known as object properties. The keys are known as “property names”, and the values can be various data types: strings, numbers, booleans, arrays, functions, and other (nested) objects.

If an object’s property’s value is a function, then that property is called a method.

Object literals and object accessors

Here’s an object literal:

{}

We’ve just added a brand new empty object to memory. However, we have no way to get it back from memory, so let’s add a binding to our object, by using a variable:

let car = {}

Now we can access our new car object, like this:

car; // returns: {}

Nesting objects in other objects in JavaScript

In JS, this is really easy to do:

let vehicles = {};
vehicles.car = car;
vehicles.boat = boat;

Inspecting vehicles returns two nested objects now:

vehicles;
// returns: { car: {...}, bike: {...} }

Currently, we don’t have any properties in our object. To work with object properties, we need a way to access them. We do that using object accessors.

Working with object properties

We use object accessors to perform CRUD operations on our objects’ properties. There are two ways to access an object’s properties:

  1. Using the dot notation
  2. Using the brackets notation

Let’s create a new property in our car object:

car.color = "red";

Now we can inspect the car object again…

car;

…and we’ll get this:

{color: "red"}

Let’s read the color property’s value, like this:

car.color; // returns: "red"

To update an object property, we just re-assign it to a different value:

car.color = "blue";

Finally, we can also delete a property, like so:

delete car.color;

After we’ve deleted it, trying to access the color property returns undefined. This means that the color property no longer exists on the car object, because any property that doesn’t exist on an object will return undefined. For example:

car.year; // returns: undefined
car.make; // returns: undefined
car.model; // returns: undefined

Maybe you’ve thought to yourself at this point: “great, that means I can just set an object’s key to undefined, and that’s gonna be the same as if I’d deleted it”. Actually, that’s not true. If you try to delete an object’s property by setting its key to the value of undefined, that’s exactly what you’d get:

let car = {};
car.year = "2020";
car; // returns: {year: "2020"}
car.year = undefined;
car; // returns: {year: undefined}

Running CRUD operations on object properties in JavaScript using brackets notation

So far we’ve seen the dot notation to perform CRUD operations on a JavaScript object’s properties. Now let’s re-write them using the bracket notation:

let boat = {};
boat["color"] = "red";  // create
boat["color"];          // read
boat["color"] = "blue"; // update
delete boat["color"];   // delete

Difference between the dot notation and the brackets notation for object access

Here’s a quick list of differences:

  1. In dot notation, property keys can only be alphanumeric characters, the $, and the _, and they can’t begin with a number.
  2. In bracket notation, property keys must be strings, but you can name them with any character you like (even spaces!)
  3. Values inside bracket notation are evaluated. Thus, the following example is possible with the brackets notation.

Using brackets notation, we can use variables to access object keys:

let car = {
    year: "2020",
    color: "red",
    make: "Toyota",
    speed: 200
};

let s = 'speed';
let howFast = car[s];
console.log(`The ${car.make} is ${howFast} km/hour`);

Using ternary expressions to determine the value of a property

This is entirely possible. We do it like this:

let areYouSelling = prompt(' Are you selling the car?Type "y" or "n" ');
let car = {
    onSale: areYouSelling == "y" ? true : false,
    year: "2020",
    color: "red",
    make: "Toyota",
    speed: 200
}

Evaluating expressions inside objects using the brackets notation

Here’s how to do it:

let name = "Full Name";
let scientist = {
    [name]: "Albert Einstein",
}
scientist; // returns: {Full Name: "Albert Einstein"}

Now we can access the Full Name property of the scientist object, like this:

scientist["Full Name"]

However, since our key has a space in it, we can’t acess it with the dot notation:

scientist."Full Name"

Trying to do the above will result in the following error:

Uncaught SyntaxError: Unexpected string

Listing out object properties

Here’s a bike object:

let bike = {};
bike.color = "red";
bike.speed = "50 km/h";
bike.price = "500 dollars";
bike.year = "2020";
bike.condition = "new";
bike.start = () => ( console.log("engine revving") );

To list out the object properties, we need to use the for in loop:

for(const propertyKey in bike) {
    console.log(`The value of ${propertyKey} is: ${bike[propertyKey]}`)
}

Here is the output:

The value of color is: red
The value of speed is: 50 km/h
The value of price is: 500 dollars
The value of year is: 2020
The value of condition is: new
The value of start is: () => ( console.log("engine revving") );

The code above doesn’t check if only the bike object’s properties are iterated over. To do that, we need to include the built-in hasOwnProperty method:

for(const propertyKey in bike) {
    if( bike.hasOwnProperty(propertyKey) ) {
        console.log(`The value of ${propertyKey} is: ${bike[propertyKey]}`)
    }
}

We don’t always have to check if we’re iterating over object’s own properties. Why? Because some methods that exist on objects do this filtering for us. One such object method is the keys method.

Object.keys(bike)
// returns: ["color", "speed", "price", "year", "condition", "start"]

As demonstrated in the code above, Object.keys returns an array of the keys of the object we give it as argument. We can then iterate over these keys, for example, like this:

Object.keys(bike).forEach( key => console.log(key) )
/*
returns:
color
speed
price
year
condition
start
*/

We could have written it using the for of as well:

for(const propertyKey of Object.keys(bike) ) {
        console.log(`The value of ${propertyKey} is: ${bike[propertyKey]}`)
}

Why the for of and not for in? Because we’re looping over an array.

Remember: for of loops over arrays; for in loops over objects.

ES8 brought some great additions to what was available (as described above). Specifically, we have two new methods on the Object now:

  1. the values method
  2. the entries method

Just like Object.keys returns an array of a given object’s keys, the values method returns an array of values:

for( const val of Object.values(bike) ) {
    console.table(val)
}

The Object.entries() method brings the game to a whole new level:

for( const [key, val] of Object.entries(bike) ) {
    console.log(key, ": ", val)
}

The above code will return:

color :  red
speed :  50 km/h
price :  500 dollars
year :  2020
condition :  new
start :  () => ( console.log("engine revving") )

Remember that the Object.values and the Object.entries are ES8 methods, so if your site visitors are using older browsers, you might need to take this into account and address this possible issue accordingly.

JavaScript objects are “pass-by-reference”

In JS, as one of many JavaScript’s optimizations, if you assign an existing object to a new variable, you’ve just created a new binding (i.e a new variable), but this new binding is still pointing to the exact same object.

The trick is that, if you update any of the object members using any of the bindings, you are changing the “underlying” object directly. In other words, regardless of which binding you’ve used to update an object’s property, this update will affect all the variables that point to that object.

Here’s an example to see this in practice.

First, let’s add a new sportsCar object:

let sportscar = {
    speed: 300
}

Now, let’s assign the sportsCar oject to another variable:

let porsche = sportsCar;

Now, let’s update the speed of our porsche:

porche.speed = 400;

Finally, let’s prove that the speed property was also updated on the sportCar variable:

sportsCar.speed; // returns: 400

Once you really understand how the pass-by-reference of objects works in JavaScript, this behavior becomes the expected, normal behavior. You know that this is just the way that it works - it becomes normal and expected.

Contrary to objects, primitives are “pass-by-value”, which means that they are pointing to completely separate places in memory. Let’s run the previous example, only this time we’ll use primitives:

let sportsCarSpeed = 300;
let porscheSpeed = sportsCarSpeed;
let porscheSpeed = 400;
porscheSpeed; // returns: 400
sportsCarSpeed; // returns: 300

In conclusion, the bindings for primitives point to separate addresses in memory. That’s why we say primitives are pass-by-value, and objects are pass-by-reference.

Passing objects to functions

Let’s pass an object to the console.log function:

console.log({ speed: 300, color: "red" })

If we ran the above code, our object will be logged to the console:

{ speed: 300, color: "red" }

If we slightly changed our code to run console.table instead of console.log, we’d get this back:

-----------------------------
| (index)    | Value        |
-----------------------------
| speed      | 300          |
-----------------------------
| color      | "red"        |
-----------------------------

We can draw two conclusions from the above:

  1. We can simply “plop” object literals into functions
  2. What the given function will do with our object depends on that function’s definition.

Now we can define a custom function declaration that expects an object:

function printDetails({first, second, third}) {
    console.log(`${first}, ${second}, ${third}`)
}

Now we can call it like this:

let first = "a";
let second = "b";
let third = "c";
printDetails({first, second, third}); // returns: a, b, c

We can also set the default values for the passed-in object properties in our function declaration’s definition:

function printDetails({
    first = 'a',
    second = 'b',
    third = 'c'
}) {
    console.log(`
        ${first}, 
        ${second}, 
        ${third}
    `)
}

This way, we can call the printDetails function without passing it a complete object. Actually, it will work even if we pass it an empty object, like this:

printDetails({}); // returns: a, b, c

Or we can pass it only some parameters, and leave out others:

printDetails({first: 123}); // returns: 123, b, c

Accessing object properties from object methods in JS

To access the object properties from inside our object’s methods, we use the this keyword, like so:

let prices = {
    clothes: 50,
    food: 100,
    calculateTotal() {
        console.log(`${this.clothes} + ${this.food}`)
    }
}
prices.calculateTotal(); // logs out: 150

That’s it. Accessing object variables from inside of object methods is easy.

Using Object.create method

The Object.create method is another way to create a new object. The new object is also linked to an existing object’s prototype.

The built-in Object.create method takes two arguments:

  1. The first argument is an object which will be the prototype of the new object
  2. The second argument is an object which will be the properties of the new object

Thus, in pseudo code, we can write it like this:

Object.create( {the prototype object}, {the properties object} )

In practice, we could do something like this:

let protoObj = {
    sayHi() {
        console.log("Hi!")
    }
}
let propertiesObj = {
    name: {
        writable: true,
        configurable: true,
        value: "John"
    }
}

let player1 = Object.create(protoObj, propertiesObj);

console.log(player1.name); // Logs "John" to console
player1.sayHi(); // Logs "Hi" to console

Here is a list of reasons to use the Object.create method:

  1. We control how the prototype is set up. With the object literal and object constructor approaches, we cannot do this (we can, however, override the prototype if we need to).
  2. We don’t invoke the constructor function when we use Object.create
  3. This approach is good when we want to build objects dynamically; for example, have an object factory function and build objects with different prototypes based on what protoObj parameter we gave it.

Built-in objects in JavaScript

Way, way back, in 1996, when the JavaScript language was still very young, it only had these built-in objects: Date, Math, String, Array, and Object.

However, over the years, JavaScript has grown, and today there are many, many, built-in object in JavaScript.

Since it would be too difficult to list out all of the objects in JavaScript in detail, here’s just a quick overview of built-in JavaScript objects, grouped by category:

  1. Value properties (Infinity, NaN, undefined, globalThis)
  2. Function properties (eval, uneval, isFInite, isNaN, parseFloat, parseInt, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent)
  3. Fundamental objects (on which all other objects are based): Object, Functioon, Boolean, Symbol
  4. Error objects: Error, AggregateError, EvalError, InternalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError
  5. Numbers and date objects (Number, BigInt, Math, and Date)
  6. Text processing (String and RegExp)
  7. Indexed collections: Array, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, BigIng64Array, BigUint64Array
  8. Keyed collections: Map, WeakMap, Set, WeakSet
  9. Structured data (structured data buffers and data coded using JSON): ArrayBuffer, SharedArrayBuffer, Atomics, DataView, JSON
  10. Control abstraction objects: Promise, Generator, GeneratorFunction, AsyncFunction, Iterator, AsyncIterator
  11. Reflection objects: Reflect and Proxy
  12. Internationalization objects: Intl, Intl.Collator, Intl.DateTimeFormat, Intl.ListFormat, Intl.NumberFormat, Intl.PluralRules, Intl.RelativeTimeFormat, Intl.Locale
  13. WebAssembly objects: WebAssembly, WebAssembly.Mdule, WebAssembly.Instance, WebAssembly.Memory, WebAssembly.Table, WebAssembly.CompileError, WebAssembly.LinkError, WebAssembly.RuntimeError
  14. Other: arguments (an array-like object accessibleinside functions, holding the values of the arguments passed to that function)

Now that we see that it’s a big list, but fortunately, still a finite one, it means we can make a long-term plan to learn about the ins and outs of all these objects - in due time.

For now, let’s go back to the basics and cover just a few of the more important built-in objects in JavaScript: String, RegExp, JSON, and Math objects.

String object

The String object wraps the primitive string data type to give it superpowers: the ability to have methods, just like other objects.

To build a String object, we use a constructor with the keyword new:

let car = "Porsche";
let carStringObj = new String(car);

The String object comes with these properties:

  1. constructor (returning the reference to the String that built the object)
  2. length (returning the number of characters of the string)
  3. prototype (giving us the ability to add and override properties and methods on an object)

The String object also has a number of methods:

  1. charAt returns the character for a given position; for example, "Porsche".charAt(6) returns the letter “e”
  2. charCodeAt similar to the above, only it returns the unicode value
  3. concat appends two strings together
  4. indexOf returns the index of the first occurence of the specified character in the string; if nothing is found, it returns a -1; for example, "Porsche".indexOf("e") returns 6, and "Porsche".indexOf("x") returns -1
  5. lastIndexOf works similar to the previous method, only this time instead of the first occurence, returns the last occurence in the word; for example: "Mississipi".lastIndexOf("i") returns 9, while "Mississipi".indexOf("i") returns 1.
  6. localeCompare returns a number showing where a string appears in relation to another string in the sort order (i.e if it comes before or after it); for example: "a".localeCompare("z") returns -1, because a indeed comes before z; "a".localeCompare("a") returns 0, while "z".localeCompare("a") returns 1 because “z” comes after “a”.
  7. match matches a string against a regular expression. We’ll see an example for it after this list.
  8. replace matches a string agianst a regular expression, then replaces the matched string with another string (example at the end of this list)
  9. search performs a match between a regex and a string
  10. slice takes out a “slice” of characters from a string “pie”
  11. split splits a String object into an array of strings, separating the string into substrings using this syntax: String.split([separator][, limit]); for example: "a s d f".split(" ", 2) returns: ["a", "s"]
  12. substr
  13. substring
  14. toLocaleLowerCase
  15. toLocaleUpperCase
  16. toLowerCase
  17. toString
  18. toUpperCase
  19. valueOf
  20. anchor
  21. big
  22. blink
  23. bold
  24. fixed
  25. fontcolor
  26. fontsize
  27. italics
  28. link
  29. small
  30. strike
  31. sub
  32. sup

Here’s an example for the String.prototype.match method:

let string = "JavaScript";
let regex = /[A-Z]/g;
let matched = string.match(regex);
console.log(matched); // returns: Array ["J", "S"];
matched = matched.join();
console.log(matched); // returns: "JS"

The String.prototype.replace method follows this syntax:

String.replace(regex/substring, newSubStr/function[, flags]);

The arguments explanation:

  • regex is a regular expression object
  • substr is the string to be replaced by newSubStr
  • newSubStr is the string that replaces the substring received in parameter #1
  • function is the fuction to be run to provide the new substring
  • flags is a string providing regex flags (g for global match, m for match over multiple lines, etc). This parameter is used only if the first parameter is a string

Here’s an example for the String.prototype.replace method:

let regex = /apples/gi;
let str = "Apples are round, apples are juicy";
let newStr = str.replace(regex, "oranges");
console.log(newStr); // returns: "oranges are round, oranges are juicy"

RegExp object

In general, a regular expression is a number of characters, forming a sequence, such as, for instance: @example.com. Obviously, regular expressions are arbitrary sequences of characters. They are often abbreviated to “regex” or “regexp”, or, in case of JavaScript, to RegExp.

In the previous paragraph, we used a sequence of characters, @example.com. One likely use case would be form input validation. If a user was trying to log into an imaginary www.example.com, and if that user needed to provide an email address that matches the domain, we’d need to check the form input for existance of @example.com.

Another use case for regular expression is in string searching algorithms, for example, running a search and replace operation in a piece of software.

Essentially, we’re trying to match for a specific sequence of characters in a larger, arbirtrary sequence of characters.

In JavaScript, the entire sequence of characters we’re performing the search on is passed to the built-in RegExp object’s test method. The RegExp object itself contains the actual pattern of characters we’re trying to find.

Using RegExp in JavaScript

Remember the built-in Object object in JavaScript? We can build a new object from this prototype, by using the object literal syntax:

{}

Similarly, we can use the built-in RegExp object to build a new regular expression from the RegExp prototype:

//

And just like we need to add a binding (variable) to store the object literal, we need to do the same for the RegExp object, if we want to work with either of them. For example:

let pattern = /whatever/;

Now we can find the pattern (i.e the sequence of characters) in a larger string:

let largerString = `
    This is a larger string that has the string "whatever" inside of it.
`

let pattern = /whatever/;

pattern.test(largerString); // returns: true

The test method on our pattern RegExp object will return true if the string that we pass it contains the pattern. Otherwise, it will return false.

For example:

let fullText = `
    This is just a random text we're be running our search on.
`

let sequence = /example/;

sequence.test(fullText); // returns: false

The test function returned false this time because the word “example” doesn’t exist in our fullText string. Note that the test function is somewhat similar to its String object’s match method. The difference is in what gets returned: for the String.prototype.match the returned value is an array with matched characters, while the RegExp.prototype.test returns a boolean.

Date object

The Date object stores data about a specific moment in time. For example:

let rightNow = new Date();
rightNow; // returns: Date Sat Apr 18 2019 02:04...

If we don’t pass any arguments to the Date constructor, the new object will be the current date and time. To get any date in the past, simply pass it as a string argument to the Date constructor:

let y2k = new Date('1 January 2000');
y2k;
/*
returns: 
Date Sat Jan 01 2000 00:00:00 GMT-0500 (Eastern Standard Time)
*/

There are several formats we can use to add new dates. For example:

let partyLikeIts1999 = new Date('1999 12 31');
let partyLikeIts1999 = new Date('31 December 1999');
let partyLikeIts1999 = new Date('Friday, December 31, 1999');

The toString method exists on all objects, including the Date object. Thus, we can convert our dates from objects to strings, like this:

partyLikeIts1999.toString(); 
/*
returns:
"Sat Apr 18 2019 02:04:12 GMT-0400 (Eastern Daylight Time)"
*/

Although it’s possible to write the argument passed to the date constructor in a variety of formats, a more consistent approach it to pass each piece of information as a separate argument, like this:

let date = new Date(year, month, day, hour, minutes, seconds, milliseconds)

Now we can re-build our y2k date, like this:

let y2k = new Date(2000, 1, 1);

JSON (JavaScript Object Notation)

JSON is utilizing the fact that JS objects are so easy to construct, with simple key-value pairings. In 2001, when he invented JSON, did Douglas Crockford realize that it is be a great way to store data? Who knows.

The important thing is, JSON is very widely used for data transfer in APIs, as well as data storage and retrieval in web apps in general.

There is a slight difference between regular JavaScript objects and JSON objects. While JSON format is syntactically correct JavaScript code, it still needs to follow several rules to be considered valid JSON:

  1. Keys must be surrounded with double quotes.
  2. Only values that are allowed are double-quoted strings, numbers, true, false, null, objects, and arrays (functions are not allowed)

That’s basically it!

For some additional JSON quirks, here’s an interesting, somewhat advanced article on the topic.

An important thing to remember: JSON is just a string. Specifically formatted string, but string, nonetheless.

Here’s an example:

let cars = '{"vw":{"model":"Golf"}, "toyota":{"model":"Corolla"}}'

There is a global JSON object in JavaScript, and it comes with its own methods that helps us work with JSON.

One such method is parse, and using it, we can convert a JSON string into a JavaScript object:

let cars = '{"vw":{"model":"Golf", "current price": 2000}, "toyota":{"model":"Corolla", "current price": "3000"}}';
JSON.parse(cars);
/*
returns:
{ 
    vw: {
        model: "Golf",
        "current price": 2000
    },
    toyota: {
        model: "Corolla",
        "current price": "3000"
    }
}
*/

We can also run the stringify method to convert a plain JavaScript object to a JSON string:

let carsObj = { 
    vw: {
        model: "Golf",
        "current price": 2000
    },
    toyota: {
        model: "Corolla",
        "current price": "3000"
    }
};
JSON.stringify(carsObj);
/*
returns:
{ 
    vw: {
        model: "Golf",
        "current price": 2000
    },
    toyota: {
        model: "Corolla",
        "current price": "3000"
    }
}
*/

If we run JSON.stringifyon an object that has methods inside of it, the stringify method will just skip them. For example, the below object’s sailAway method will simply be ignored, and not included in the resulting JSON string:

let boatsObj = {
    slowBoat: {
        speed: 20
    },
    fastBoat: {
        speed: 40
    },
    sailAway() {
        console.log("Sailing away");
    }
}
JSON.stringify(boatsObj);
/*
returns:
'{"slowBoat":{"speed":20},"fastBoat":{"speed":40}}"
*/

A practical use case for JSON strings is to, for example, save application data inside localStorage of a user’s browser. Of course, to store an object of data we first need to JSON.stringify it, and to retreive it so that our application can use it, we need to run the JSON.parse method on it.

Math object

The Math object gives us built-in constants:

  1. Math.PI (the number PI)
  2. Math.SQRT (the square root of 2)
  3. Math.SQRT1_2 (the square root of 1/2)
  4. Math.E (the Euler’s constant)
  5. Math.LN2 (the natural logarithm of 2)
  6. Math.LN10 (the natural logarithm of 10)
  7. Math.LOG2E (the log base 2 of Euler’s constant)
  8. Math.LOG10E (the log base 10 of Euler’s constant)

The Math object also comes with a number of built-in methods:

  1. Rounding methods (Math.ceil, Math.floor, Math.round, Math.trunc)
  2. Random number between 0 and 1 is the result of running Math.random
  3. Math.pow; for example, Math.pow(10, 3) returns the result of the number 10, cubed
  4. Math.sqrt for the square root of a given number, e.g. Math.sqrt(100) returns 10
  5. Math.cbrt for the cube root of a given number, e.g. Math.cbrt(1000) returns 10
  6. Math.exp raises a number to the power of Euler’s constant
  7. Math.hypot() calculates the hypothenuse, i.e the square root of all its arguments squared, then added up. For example, Math.hypot(4,3) returns the square root of: “(4 squared) plus (3 squared)”, which is, of course, 5
  8. Math.abs gives a number’s absolute value, thus Math.abs(-42) returns 42
  9. Logarithmic methods: Math.log, Math.log2, Math.log10
  10. Methods to return the minimum and maximum values of all the arguments provided, e.g: Math.min(4,2,1) returns 1, while Math.max(3,2,1) retunrs 3.
  11. Trigonometric functions: Math.sin, Math.asin, Math.cos, Math.acos, Math.tan, Math.atan, Math.sinh, Math.asinh, Math.cosh, Math.acosh, Math.tanh, Math.atanh

Note that the Math object has no constructor and we cannot instantiate it before we use it! We just straight up use it.

Using the Math object, we can easily emulate a six-sided dice:

1
2
3
4
5
function throwDice() {
    let randomNum = Math.random() * 6;
    randomNum = Math.ceil(randomNum);
    console.log(randomNum);
}

On line 2 of the above code, we get a random number between 0 and 1 with Math.random. We then multiply it by 6, and finally we store it in the randomNum variable.

On line 3, we get rid of the decimals by running Math.floor on randomNum, and we assign the new number to the existing randomNum.

On line 4, we log the value of randomNum to the console.

Feel free to check out my work here: