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.
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:
- Using the dot notation
- 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:
- In dot notation, property keys can only be alphanumeric characters, the
$
, and the_
, and they can’t begin with a number. - In bracket notation, property keys must be strings, but you can name them with any character you like (even spaces!)
- 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:
- the
values
method - 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:
- We can simply “plop” object literals into functions
- 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:
- The first argument is an object which will be the prototype of the new object
- 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:
- 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).
- We don’t invoke the constructor function when we use
Object.create
- 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:
- Value properties (
Infinity
,NaN
,undefined
,globalThis
) - Function properties (
eval
,uneval
,isFInite
,isNaN
,parseFloat
,parseInt
,decodeURI
,decodeURIComponent
,encodeURI
,encodeURIComponent
) - Fundamental objects (on which all other objects are based):
Object
,Functioon
,Boolean
,Symbol
- Error objects:
Error
,AggregateError
,EvalError
,InternalError
,RangeError
,ReferenceError
,SyntaxError
,TypeError
,URIError
- Numbers and date objects (
Number
,BigInt
,Math
, andDate
) - Text processing (
String
andRegExp
) - Indexed collections:
Array
,Int8Array
,Uint8Array
,Uint8ClampedArray
,Int16Array
,Uint16Array
,Int32Array
,Uint32Array
,Float32Array
,Float64Array
,BigIng64Array
,BigUint64Array
- Keyed collections:
Map
,WeakMap
,Set
,WeakSet
- Structured data (structured data buffers and data coded using JSON):
ArrayBuffer
,SharedArrayBuffer
,Atomics
,DataView
,JSON
- Control abstraction objects:
Promise
,Generator
,GeneratorFunction
,AsyncFunction
,Iterator
,AsyncIterator
- Reflection objects:
Reflect
andProxy
- Internationalization objects:
Intl
,Intl.Collator
,Intl.DateTimeFormat
,Intl.ListFormat
,Intl.NumberFormat
,Intl.PluralRules
,Intl.RelativeTimeFormat
,Intl.Locale
- WebAssembly objects:
WebAssembly
,WebAssembly.Mdule
,WebAssembly.Instance
,WebAssembly.Memory
,WebAssembly.Table
,WebAssembly.CompileError
,WebAssembly.LinkError
,WebAssembly.RuntimeError
- 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:
constructor
(returning the reference to theString
that built the object)length
(returning the number of characters of the string)prototype
(giving us the ability to add and override properties and methods on an object)
The String
object also has a number of methods:
charAt
returns the character for a given position; for example,"Porsche".charAt(6)
returns the letter “e”charCodeAt
similar to the above, only it returns the unicode valueconcat
appends two strings togetherindexOf
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 -1lastIndexOf
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.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”.match
matches a string against a regular expression. We’ll see an example for it after this list.replace
matches a string agianst a regular expression, then replaces the matched string with another string (example at the end of this list)search
performs a match between a regex and a stringslice
takes out a “slice” of characters from a string “pie”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"]
substr
substring
toLocaleLowerCase
toLocaleUpperCase
toLowerCase
toString
toUpperCase
valueOf
anchor
big
blink
bold
fixed
fontcolor
fontsize
italics
link
small
strike
sub
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:
- Keys must be surrounded with double quotes.
- 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.stringify
on 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:
Math.PI
(the number PI)Math.SQRT
(the square root of 2)Math.SQRT1_2
(the square root of 1/2)Math.E
(the Euler’s constant)Math.LN2
(the natural logarithm of 2)Math.LN10
(the natural logarithm of 10)Math.LOG2E
(the log base 2 of Euler’s constant)Math.LOG10E
(the log base 10 of Euler’s constant)
The Math
object also comes with a number of built-in methods:
- Rounding methods (
Math.ceil
,Math.floor
,Math.round
,Math.trunc
) - Random number between 0 and 1 is the result of running
Math.random
Math.pow
; for example,Math.pow(10, 3)
returns the result of the number 10, cubedMath.sqrt
for the square root of a given number, e.g.Math.sqrt(100)
returns 10Math.cbrt
for the cube root of a given number, e.g.Math.cbrt(1000)
returns 10Math.exp
raises a number to the power of Euler’s constantMath.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, 5Math.abs
gives a number’s absolute value, thusMath.abs(-42)
returns 42- Logarithmic methods:
Math.log
,Math.log2
,Math.log10
- Methods to return the minimum and maximum values of all the arguments provided, e.g:
Math.min(4,2,1)
returns1
, whileMath.max(3,2,1)
retunrs3
. - 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.