What are Objects
An object is a built-in data type for storing key-value pairs. Data inside objects are unordered, and the values can be of any type.
Creating Object Literals
Objects can be assigned to variables just like any JavaScript type. We use curly braces, {}
, to designate an object literal:
We fill an object with unordered data. This data is organized into key-value pairs. A key is like a variable name that points to a location in memory that holds a value.
A key’s value can be of any data type in the language including functions or other objects.
We make a key-value pair by writing the key’s name, or identifier, followed by a colon and then the value. We separate each key-value pair in an object literal with a comma (,
). Keys are strings, but when we have a key that does not have any special characters in it, JavaScript allows us to omit the quotation marks:
The spaceship
object has two properties Fuel Type
and color
. 'Fuel Type'
has quotation marks because it contains a space character.
Accessing Properties
There are two ways we can access an object’s property. Let’s explore the first way— dot notation, .
. You’ve used dot notation to access the properties and methods of built-in objects and data instances:
With property dot notation, we write the object’s name, followed by the dot operator and then the property name (key):
If we try to access a property that does not exist on that object, undefined
will be returned.
Bracket Notation
The second way to access a key’s value is by using bracket notation, [ ]
:
To use bracket notation to access an object’s property, we pass in the property name (key) as a string.
We must use bracket notation when accessing keys that have numbers, spaces, or special characters in them. Without bracket notation in these situations, our code would throw an error.
With bracket notation you can also use a variable inside the brackets to select the keys of an object. This can be especially helpful when working with functions:
If we tried to write our returnAnyProp()
function with dot notation (objectName.propName
) the computer would look for a key of 'propName'
on our object and not the value of the propName
parameter.
Property Assignment
Once we’ve defined an object, we’re not stuck with all the properties we wrote. Objects are mutable meaning we can update them after we create them!
We can use either dot notation, .
, or bracket notation, []
, and the assignment operator, = to add new key-value pairs to an object or change an existing property.
One of two things can happen with property assignment:
- If the property already exists on the object, whatever value it held before will be replaced with the newly assigned value.
- If there was no property with that name, a new property will be added to the object.
It’s important to know that although we can’t reassign an object declared with const
, we can still mutate it, meaning we can add new properties and change the properties that are there.
You can delete a property from an object with the delete
operator.
Object-Methods
When the data stored on an object is a function we call that a method. A property is what an object has, while a method is what an object does.
Do object methods seem familiar? That’s because you’ve been using them all along! For example console
is a global JavaScript object and .log()
is a method on that object. Math
is also a global JavaScript object and .floor()
is a method on it.
We can include methods in our object literals by creating ordinary, colon-separated key-value pairs. The key serves as our method’s name, while the value is an anonymous function expression.
With the new method syntax introduced in ES6 we can omit the colon and the function
keyword.
Object methods are invoked by appending the object’s name with the dot operator followed by the method name and parentheses:
Pass By Reference
Objects are passed by reference. This means when we pass a variable assigned to an object into a function as an argument, the computer interprets the parameter name as pointing to the space in memory holding that object. As a result, functions which change object properties actually mutate the object permanently (even when the object is assigned to a const
variable).
Looping Through Objects
Loops are programming tools that repeat a block of code until a condition is met. We learned how to iterate through arrays using their numerical indexing, but the key-value pairs in objects aren’t ordered! JavaScript has given us alternative solution for iterating through objects with the for...in
syntax .
for...in
will execute a given block of code for each property in an object.
Our for...in
will iterate through each element of the spaceship.crew
object. In each iteration, the variable crewMember
is set to one of spaceship.crew
‘s keys, enabling us to log a list of crew members’ role and name
.
The this Keyword
Objects are collections of related data and functionality. We store that functionality in methods on our objects:
In our goat
object we have a .makeSound()
method. We can invoke the .makeSound()
method on goat
.
Nice, we have a goat
object that can print baaa
to the console. Everything seems to be working fine. What if we wanted to add a new method to our goat
object called .diet()
that prints the goat
‘s dietType
?
That’s strange, why is dietType
not defined even though it’s a property of goat
? That’s because inside the scope of the .diet()
method, we don’t automatically have access to other properties of the goat
object.
Here’s where the this
keyword comes to the rescue. If we change the .diet()
method to use the this
, the .diet()
works! :
The this
keyword references the calling object which provides access to the calling object’s properties. In the example above, the calling object is goat
and by using this
we’re accessing the goat
object itself, and then the dietType
property of goat
by using property dot notation.
Arrow Functions and this
We saw in the previous exercise that for a method, the calling object is the object the method belongs to. If we use the this
keyword in a method then the value of this
is the calling object. However, it becomes a bit more complicated when we start using arrow functions for methods. Take a look at the example below:
In the comment, you can see that goat.diet()
would log undefined
. So what happened? Notice that the .diet()
method is defined using an arrow function.
Arrow functions inherently bind, or tie, an already defined this
value to the function itself that is NOT the calling object. In the code snippet above, the value of this
is the global object, or an object that exists in the global scope, which doesn’t have a dietType
property and therefore returns undefined
.
To read more about either arrow functions or the global object check out the MDN documentation of the global object and arrow functions.
The key takeaway from the example above is to avoid using arrow functions when using this
in a method!
Privacy
Accessing and updating properties is fundamental in working with objects. However, there are cases in which we don’t want other code simply accessing and updating an object’s properties. When discussing privacy in objects, we define it as the idea that only certain properties should be mutable or able to change in value.
Certain languages have privacy built-in for objects, but JavaScript does not have this feature. Rather, JavaScript developers follow naming conventions that signal to other developers how to interact with a property. One common convention is to place an underscore _
before the name of a property to mean that the property should not be altered. Here’s an example of using _
to prepend a property.
In the example above, the _amount
is not intended to be directly manipulated.
Even so, it is still possible to reassign _amount
:
Getters
Getters are methods that get and return the internal properties of an object. But they can do more than just retrieve the value of a property! Let’s take a look at a getter method:
Notice that in the getter method above:
- We use the
get
keyword followed by a function. - We use an
if...else
conditional to check if both_firstName
and_lastName
exist (by making sure they both return truthy values) and then return a different value depending on the result. - We can access the calling object’s internal properties using
this
. InfullName
, we’re accessing boththis._firstName
andthis._lastName
. - In the last line we call
fullName
onperson
. In general, getter methods do not need to be called with a set of parentheses. Syntactically, it looks like we’re accessing a property.
Now that we’ve gone over syntax, let’s discuss some notable advantages of using getter methods:
- Getters can perform an action on the data when getting a property.
- Getters can return different values using conditionals.
- In a getter, we can access the properties of the calling object using
this
. - The functionality of our code is easier for other developers to understand.
Another thing to keep in mind when using getter (and setter) methods is that properties cannot share the same name as the getter/setter function. If we do so, then calling the method will result in an infinite call stack error. One workaround is to add an underscore before the property name like we did in the example above.
Setters
Along with getter methods, we can also create setter methods which reassign values of existing properties within an object. Let’s see an example of a setter method:
Notice that in the example above:
- We can perform a check for what value is being assigned to
this._age
. - When we use the setter method, only values that are numbers will reassign
this._age
- There are different outputs depending on what values are used to reassign
this._age
.
Then to use the setter method:
Setter methods like age
do not need to be called with a set of parentheses. Syntactically, it looks like we’re reassigning the value of a property.
Like getter methods, there are similar advantages to using setter methods that include checking input, performing actions on properties, and displaying a clear intention for how the object is supposed to be used. Nonetheless, even with a setter method, it is still possible to directly reassign properties. For example, in the example above, we can still set ._age
directly:
Factory Functions
So far we’ve been creating objects individually, but there are times where we want to create many instances of an object quickly. Here’s where factory functions come in. A real world factory manufactures multiple copies of an item quickly and on a massive scale. A factory function is a function that returns an object and can be reused to make multiple object instances. Factory functions can also have parameters allowing us to customize the object that gets returned.
Let’s say we wanted to create an object to represent monsters in JavaScript. There are many different types of monsters and we could go about making each monster individually but we can also use a factory function to make our lives easier. To achieve this diabolical plan of creating multiple monsters objects, we can use a factory function that has parameters:
In the monsterFactory
function above, it has four parameters and returns an object that has the properties: name
, age
, energySource
, and scare()
. To make an object that represents a specific monster like a ghost, we can call monsterFactory
with the necessary arguments and assign the return value to a variable:
Now we have a ghost
object as a result of calling monsterFactory()
with the needed arguments. With monsterFactory
in place, we don’t have to create an object literal every time we need a new monster. Instead, we can invoke the monsterFactory
function with the necessary arguments to take over the world make a monster for us!
How to call
Property Value Shorthand
ES6 introduced some new shortcuts for assigning properties to variables known as destructuring.
In the previous exercise, we created a factory function that helped us create objects. We had to assign each property a key and value even though the key name was the same as the parameter name we assigned to it. To remind ourselves, here’s a truncated version of the factory function:
Imagine if we had to include more properties, that process would quickly become tedious! But we can use a destructuring technique, called property value shorthand, to save ourselves some keystrokes. The example below works exactly like the example above:
Notice that we don’t have to repeat ourselves for property assignments!
Destructured Assignment
We often want to extract key-value pairs from objects and save them as variables. Take for example the following object:
If we wanted to extract the residence
property as a variable, we could use the following code:
However, we can also take advantage of a destructuring technique called destructured assignment to save ourselves some keystrokes. In destructured assignment we create a variable with the name of an object’s key that is wrapped in curly braces { }
and assign to it the object. Take a look at the example below:
Look back at the vampire
object’s properties in the first code example. Then, in the example above, we declare a new variable residence
that extracts the value of the residence
property of vampire
. When we log the value of residence
to the console, 'Transylvania'
is printed.
We can even use destructured assignment to grab nested properties of an object:
Built-in Object Methods
Object.keys()
The Object.keys()
static method returns an array of a given object’s own enumerable string-keyed property names.
Object.entries()
The Object.entries()
static method returns an array of a given object’s own enumerable string-keyed property key-value pairs.
Object.assign()
The Object.assign()
static method copies all enumerable own properties from one or more source objects to a target object. It returns the modified target object.