Objects
Problem Statement
Array
s are great for representing simple, ordered data sets, but they're generally not so great at modeling a more complex structure. For that, we need Object
s.
ASIDE: Un-helpfully JavaScript called this thing with curly braces (
{}
) anObject
.It's similar to Ruby's
Hash
, Python'sDictionary
or C-like languages'struct
(ure).There is a relationship to object-oriented programming where we create classes and instances, but that's not what's at play here. Don't confuse the two.
When JavaScript was created, it was thinking it'd only be a lightweight language for doing DOM-based programming. It didn't think that it would ever need "object orientation" like Java or C++ did. But JavaScript turned out to be way more popular than even it expected and later gained that class- and instance-based thing known as "Object-Oriented Programming."
So for this lesson and the next few that follow, try not to think of
Object
as being like "Object-Oriented Programming," but instead think of it as being closer to a key/value based data structure.
Objectives
Identify JavaScript
Object
sAccess a value stored in an
Object
Add a property to an
Object
Use convenience methods like
Object.assign()
andObject.keys()
Remove a property from an
Object
Identify the Relationship Between Arrays and Objects
Identify JavaScript Objects
Let's think about how we could represent the address of Flatbook HQ in JavaScript.
Addresses are made up of words and numbers, so at first it might make sense to store the address as a string:
That looks decent enough, but what happens if the company moves to a different floor in the same building? We just need to modify one piece of the address, but with a string we'd have to involve some pretty complicated find-and-replace pattern matching or replace the entire thing. Instead, let's throw the different pieces of the address into an Array
:
Now, we can just grab the small piece that we want to update and leave the rest as-is:
This seems like a better solution, but it still has its drawbacks. Namely, address[1]
is a terrible way to refer to the second line of an address. What if there is no second line, e.g., ['11 Broadway', 'New York', 'NY', 10004]
? Then address[1]
will contain the city name instead of the floor number.
We could standardize it, putting an empty string in address[1]
if there's no second line in the address, but it's still poorly named. address[1]
offers very little insight into what data we should expect to find in there. It's a part of an address, sure, but which part?
To get around this, we could store the individual pieces of the address in separate, appropriately-named variables:
That's solved one issue but reintroduced the same problem we tackled in the lesson on Array
s: storing pieces of related data in a bunch of unrelated variables is not a great idea! If only there were a best-of-both-worlds solution — a way to store all of our address information in a single data structure while also maintaining a descriptive naming scheme. The data structure we're after here is the Object
.
What Is an Object?
We briefly touched on the syntax for an Object
back in the data types lesson, and let's quickly revisit that description here:
JavaScript
Object
s are a collection of properties bounded by curly braces ({ }
). The properties can point to values of any data type — even otherObject
s.
We can have empty Object
s:
Or Object
s with a single key-value pair:
When we have to represent multiple key-value pairs in the same Object
(which is most of the time), we use commas to separate them out:
For a real example, let's see our address as an Object
:
The real data in an Object
is stored in the value half of the key-value pairings. The key is what lets us access that value. In the same way we use identifiers to name variables and functions, inside an Object
we assign each value a key. We can then refer to that key and the JavaScript engine knows exactly which value we're trying to access.
Access a Value Stored in an Object
There are two ways to access values in an Object
, one of which we already learned about in the Array
s lesson: dot notation and bracket notation.
Dot Notation
With dot notation, we use the member access operator (a single period) to access values in an Object
. For example, we can grab the individual pieces of our address, above, as follows:
Dot notation is fantastic for readability, as we can just reference the bare key name (e.g., street1
or zipCode
). Because of this simple syntax, it should be your go-to strategy for accessing the properties of an Object
.
NOTE: Most people just call it dot notation or the dot operator, so don't worry too much about remembering the term member access operator.
Accessing Nonexistent Properties
If we try to access the country
property of our address
Object
, what will happen?
It returns undefined
because there is no matching key on the Object
. JavaScript is too nice to throw an error, so it lets us down gently. Keep this in mind, though: if you're seeing undefined
when trying to access an Object
's properties, it's a good indicator to recheck which properties exist on the Object
(along with your spelling and capitalization)!
Bracket Notation
With bracket notation, we use the computed member access operator, which, recall from the lesson on Array
s, is a pair of square brackets ([]
). To access the same properties as above, we need to represent them as strings inside the operator:
Bracket notation is a bit harder to read than dot notation, so we always default to the latter. However, there are two main situations in which to use bracket notation.
With Nonstandard Keys
If (for whatever reason) you need to use a nonstandard string as the key in an Object
, you'll only be able to access the properties with bracket notation. For example, this is a valid Object
:
It's impossible to access those properties with dot notation:
But bracket notation works just fine:
In order to access a property via dot notation, the key must follow the same naming rules applied to variables and functions. It's also important to note that under the hood all keys are strings. Don't waste too much time worrying whether a key is accessible via dot notation. If you follow these rules when naming your keys, everything will work out:
camelCaseEverything
Start the key with a lowercase letter
No spaces or punctuation
If you follow those three rules, you'll be able to access all of an Object
's properties via bracket notation or dot notation.
Top Tip: Always name your Object
's keys according to the above three rules. Keeping everything standardized is good, and being able to access properties via dot notation is much more readable than having to always default to bracket notation.
Accessing Properties Dynamically
The other situation in which bracket notation is required is if we want to dynamically access properties. From the lesson on Array
s, remember why we call it the computed member access operator: we can place any expression inside the brackets and JavaScript will compute its value to figure out which property to access. For example, we can access the zipCode
property from our address
Object
like so:
Pretty neat, but the real strength of bracket notation is its ability to compute the value of variables on the fly. For example:
If we try to use the mealName
variable with dot notation, it doesn't work:
With dot notation, JavaScript doesn't treat mealName
as a variable — it checks whether a property exists with a key of mealName
, only finds properties named breakfast
, lunch
, and dinner
, and so returns undefined
. Essentially, dot notation is for when you know the exact name of the property in advance, and bracket notation is for when you need to compute it when the program runs.
The need for bracket notation doesn't stop at dynamically setting properties on an already-created Object
. Since the ES2015 update to JavaScript, we can also use bracket notation to dynamically set properties during the creation of a new Object
. For example:
Let's try doing the same thing without the square brackets:
Without the square brackets, JavaScript treated each key as a literal identifier instead of a variable. Bracket notation — the computed member access operator once again shows its powers of computation!
Bracket notation will really come in handy when we start iterating over Object
s and programmatically accessing and assigning properties.
Add a Property to an Object
To add properties to an Object
, we can use either dot notation or bracket notation:
Multiple properties can have the same value:
But keys must be unique. If the same key is used for multiple properties, only the final value will be retained. The rest will be overwritten:
We can update or overwrite existing properties by assigning a new value to an existing key:
Note that modifying an Object
's properties in the way we did above is destructive. This means that we're making changes directly to the original Object
. We could encapsulate this modification process in a function, like so:
At our restaurant, we've finished serving for the day. It's time to update our mondayMenu
to the tuesdayMenu
:
Looks like our tuesdayMenu
came out perfectly. But what about mondayMenu
?
Dang! We don't serve Caesar salad on Mondays. Instead of destructively updating the original menu, is there a way to nondestructively merge the change(s) into a new Object
, leaving the original intact?
Use Convenience Methods Like Object.assign()
and Object.keys()
Object.assign()
and Object.keys()
We can create a method that accepts the old menu and the proposed change:
Then, with the ES2015 spread operator, we can copy all of the old menu Object
's properties into a new Object
:
And finally, we can update the new menu Object
with the proposed change and return the updated menu:
A Side Note: You may notice that we're using const
here, but adding a key and value. But that can't be right, since const
means constant, that is can't change. The data in a const
pointing to an Array
or Object
can still be changed, but a new value cannot be assigned to the name. Given const x = {}
it's OK to say x.dog = "Poodle"
, but it is not OK to say x = [1,2,3]
.
Anyway, back to nondestructively returning Object
s. We've got our code written, but it's quite a bit to write, and it's not very extensible. If we want to modify more than a single property, we'll have to completely rewrite our function! Luckily, JavaScript has a much better solution for us.
Object.assign()
Object.assign()
JavaScript provides us access to a global Object
Object
that has a bunch of helpful methods we can use. One of those methods is Object.assign()
, which allows us to combine properties from multiple Object
s into a single Object
. The first argument passed to Object.assign()
is the initial Object
in which all of the properties are merged. Every additional argument is an Object
whose properties we want to merge into the first Object
:
The return value of Object.assign()
is the initial Object
after all of the additional Object
s' properties have been merged in:
Pay attention to the flour
property in the above example. If multiple Object
s have a property with the same key, the last key to be defined wins out. Essentially, the last call to Object.assign()
in the above snippet is wrapping all of the following assignments into a single line of code:
A common pattern for Object.assign()
is to provide an empty Object
as the first argument. That way we're composing an entirely new Object
instead of modifying or overwriting the properties of an existing Object
. This pattern allows us to rewrite the above destructivelyUpdateObject()
function in a nondestructive way:
It's important that we merge everything into a new, empty Object
. Otherwise, we would be modifying the original Object
. In your browser's console, test what happens if the body of the above function were return Object.assign(obj, { [key]: value });
. Uh oh, back to being destructive!
Let's write a function for our restaurant that accepts an old menu and some changes we want to make and returns a new menu without modifying the old menu:
Note: For deep cloning, we need to use other alternatives because Object.assign()
copies property values.
For example, if newOfferings
did not have an updated value for hard
cheese such as:
Our output for const wednesdayMenu = createNewMenu(tuesdayMenu, newOfferings);
would look like this:
... instead of the desired outcome of this:
Bon appétit!
Object.keys()
Object.keys()
Another convenience method available on the global Object
Object
is Object.keys()
. When an Object
is passed as an argument to Object.keys()
, the return value is an Array
containing all of the keys at the top level of the Object
:
Notice that it didn't pick up the keys in the nested cheesePlate
Object
— just the keys from the properties declared at the top level within wednesdayMenu
.
NOTE: The sequence in which keys are ordered in the returned Array
is not consistent across browsers and should not be relied upon. All of the Object
's keys will be in the Array
, but you can't count on keyA
always being at index 0
of the Array
and keyB
always being at index 1
.
Remove a Property from an Object
Uh oh, we ran out of Southwestern dressing, so we have to take the salad off the menu. In JavaScript, that's as easy as:
We pass the property that we'd like to remove to the delete
operator, and JavaScript takes care of the rest. Poof! No more salad
property on the wednesdayMenu
Object
.
Identify the Relationship Between Arrays and Objects
Think back to the early lesson on data types in JavaScript. We listed off seven types into which all data falls: numbers, strings, booleans, symbols, Object
s, null
, and undefined
. Notice anything missing? Arrays!
Why isn't an Array
a fundamental data type in JavaScript? The answer is that it's actually a special type of Object
. Yes, that's right: Array
s are Object
s. To underscore this point, check out what the typeof
operator returns when we use it on an Array
:
We can set properties on an Array
just like a regular Object
:
And we can modify and access those properties, too:
In fact, everything we just learned how to do to Object
s can also be done to Array
s because Array
s are Object
s. Just special ones. To see the special stuff, let's .push()
some values into our Array
:
Cool, looks like everything's still in there. What's your guess about the Array
's .length
?
Huh, that's interesting. Surely our summary
must be the first element in the Array
, no? After all, we did add it before we .push()
ed all those values in.
Hm, then maybe it's the last element?
What the heck? Where is it?
You see, one of the 'special' features of an Array
is that its Array
-style elements are stored separately from its Object
-style properties. The .length
property of an Array
describes how many items exist in its special list of elements. Its Object
-style properties are not included in that calculation.
This brings up an interesting question: if we add a new property to an Array
that has a key of 0
, how does the JavaScript engine know whether it should be an Object
-style property or an Array
-style element?
So JavaScript used that assignment operation to add a new Array
-style element. What happens if we enclose the integer in quotation marks, turning it into a string?
This is hitting on a fundamental truth: all keys in Object
s and all indexes in Array
s are actually strings. In myArray[0]
we're using the integer 0
, but under the hood the JavaScript engine automatically converts that to the string "0"
. When we access elements or properties of an Array
, the engine routes all integers and integers masquerading as strings (e.g., '14'
, "953"
, etc.) to the Array
's special list of elements, and it treats everything else as a simple Object
property. For example:
After adding our weird '01'
property, the .length
property still returns 4
:
So it would stand to reason that Object.keys()
would only return '01'
, right?
Unfortunately not. The reason why Array
s have this behavior would take us deep inside the JavaScript source code, and it's frankly not that important. Just remember these simple guidelines, and you'll be just fine:
For accessing elements in an
Array
, always use integers.Be wary of setting
Object
-style properties on anArray
. There's rarely any reason to, and it's usually more trouble than it's worth.Remember that all
Object
keys, includingArray
indexes, are strings. This will really come into play when we learn how to iterate overObject
s, so keep it in the back of your mind.
Conclusion
We dug deep into Object
s in JavaScript. We identified what an Object
is and how to access values stored in it. We also covered how to add and remove properties, and use the convenience methods Object.assign()
and Object.keys()
. We also traced the link between Object
s and Array
s.
Resources
MDN
Last updated