Objects and Classes in JavaScript
00:00 In the previous lesson, I showed you how functions work in JavaScript. In this lesson, I’m going to talk about objects and classes. JavaScript objects and classes can be a bit tricky for a Python programmer or anyone coming from almost any C-based language. On the surface, they’re kind of what you’re used to but underneath, there are some differences that can surprise you.
00:21
You can think of JavaScript objects like a mix of Python’s dict
, data classes, and classes. They have aspects of each. Where they differ is in how the underlying declaration happens. Depending on which version of JavaScript you’re using, this can be hidden away or very much in your face.
00:38 JavaScript object declaration is prototype-based. This means that instead of declaring the class and instantiating it, you just declare the object. You can then create new objects by using the original as a prototype.
00:51
There are other languages that use this style of object creation, lists being one of the more popular. Prior to ES6, this was the only way of doing things. ES6 introduced the class
keyword, which makes the syntax more familiar to Python programmers, but the objects are still prototype-based underneath.
01:11
I’m starting out with the simplest possible object declaration, which looks very much like a Python dictionary. In the top area here, you see that I’ve declared a person
with name
, age
, and married
attributes.
01:23
Notice that, unlike Python, you don’t have to put quotes around the attribute names. This saves some typing, but can be painful if you’re switching back and forth between the two languages. In the bottom area, I’ve got a Node session running and I’m going to load the person.js
file.
01:41
In previous lessons, you’ve seen how the .load
command prints the contents of what is loaded. Moving forward, I’m going to use the magic of video editing to skip that massive text and just show you the result. Here, I’ve loaded the file and the end result is the person
object containing John Cleese’s details.
02:01
Individual attributes of the object could be accessed and modified directly through dot notation. This is like Python classes, rather than like dict
.
02:10
Here’s the result. A quick tangent on the ++
operator. When I did person.age++
, you’ll note that the value printed out by Node was 82
.
02:22
The increment did actually happen, but the ++
operator is funny. It can go before or after an item, and this changes its behavior. Before means “Increment before using it,” after means “Increment after.” Node printed out the value of 82
because the ++
is to the right of the value. In 99% of cases, this doesn’t matter, but every once in a while this can cause unexpected things in your code.
02:51 Like Python dictionaries, attributes on the object can also be accessed using square bracket notation.
03:01
Also like Python, you can dynamically add attributes to an object. Looking at the object, you can see the new value for John’s age and the new attribute funny
.
03:16
Attribute names can have spaces in them as well. Here’s the resulting person
. Note how the attributes are shown without quotes, except for the 'last name'
attribute.
03:30
The 'last name'
attribute must be accessed with square brackets and can’t be accessed using dot notation. This makes sense because the parser sees that space and thinks it is the next part of the expression. Generally, it is good practice not to have spaces in these things.
03:48 It only tends to become important when you’re using the object like a dictionary and your key is user data and has a space in it. Similar to Python, you can delete an attribute. A few more keystrokes to do it, but the concept is the same.
04:06
There it is, gone! So far, everything has been very dictionary-like. Let’s do something that is more object-like. In the top area, I’m going to show a new object, car
.
04:21
It has a function called .print_name()
. Inside of the function, you will see the keyword this
. It is similar to the idea of self
in a Python class.
04:30
On line 6, where console.log()
is called, this.make
and this.model
refer to the attributes make
and model
of the current object. Let’s load this sucker in.
04:44
Here’s the object, now I’ll call the function, and there’s the result. The person
and car
objects were declared directly. In previous lessons, I briefly spoke about using constructors to create objects.
05:00
There are a couple of ways of doing this. The first is to create a function and then use the new
keyword with that function to create a new object.
05:10
In the top area, I’ve declared a constructor function for a Truck
object. On line 2, you see the arguments to the function. On lines 3 and 4, I’m storing the arguments as attributes on the object.
05:24
This is similar to what you do in the initializer of a Python class. On line 5, I create another attribute. This one is a function. Remember that functions are just reference types, so I can assign this the same way I would any other reference type. The .print_name()
function does the same printing that I did in the car
object, also using the this
keyword. This time around I’m using backtick (`
) instead of quotes. This is like an f-string in Python.
05:52
Anything found inside of the ${}
is used as a substitution in the template. This call to console.log()
does the same thing as .print_name()
in car
, I’m just using a different way of expressing the string.
06:08
Let me load this into the Node session. And I create a new Truck
object by using the new
keyword and calling the Truck()
constructor function.
06:23
What is returned is a new instance of a Truck
object. Let me call the .print_name()
method. No surprises here. Now I’m going to create another Truck
object.
06:40
Call its .print_name()
method.
06:45 Okay, no problem. Now let’s do something funky. Remember how I said functions are just references? Well, I can redefine the reference.
07:03
The .print_name()
method now points to an arrow function that prints out the words "replaced fn"
(replaced function). Let me call it.
07:12
And there it is. Well, what about the other truck? This is where things get interesting. Let’s call truck2.print_name()
.
07:24
Did you expect that? Remember, these aren’t really classes. They’re all objects. truck
and truck2
are copies of the object created by the constructor.
07:34
Because of the way the .print_name()
method was declared, each Truck
instance gets a full copy of the function unique to the object.
07:41
Changing the declaration on the truck
instance does not impact the truck2
. instance at all. This kind of sucks. It means more memory is used for objects and is rather restricting on what you can do at the class level. At the beginning of the lesson, I commented on JavaScript objects being prototyped-based. Instead of declaring objects the Truck
way, you can access the prototype of the object and get more Python-like class behavior.
08:10
In the top area, I’m declaring a plain constructor function. This is like the Truck
, but without the .print_name()
method. After the function is declared, you can then access the prototype of the function and associate attributes with it.
08:24
The prototype is like the class in Python. By creating a new function on the prototype, it is shared across all of the Plane
objects, fixing the problem posed by the style of declaration used by Truck
.
08:38 Let me load in the plane, still skipping the boring parts,
08:44
and construct the Plane
,
08:49 and print its name. You can use the same mechanism to create shared attributes. These would be like class variables in Python.
09:03 Note that when you show the object the shared items, functions, or attributes don’t show,
09:12 but the value is definitely there. Prototypes are pretty powerful. They’re also a bit dangerous. I can demonstrate this by augmenting this conversation about planes with some snakes. Mace Windu would be angry with me.
09:28 Even system objects have prototypes. In the top area, I’ve created a new shared method for the string object that uses a regex and some string manipulation to turn camel case strings into snake case strings.
09:40 Let me load it in, and now strings have a new method on them.
09:52 See what I mean by powerful? Of course, this also means I can override any method on an object. This can be useful for things like operator overloading, but it also can be asking for trouble.
10:02
Your coworkers might not realize you changed something and could be surprised at the results. As you might imagine, writing a separate declaration for each function on your class-like thing would be pretty verbose. JavaScript ES6 introduced new syntax to make things a bit easier to write and more familiar for those coming from other languages. In the top area, I have a class called Boat
. Yay, classes! Inside the class, you’ll find a constructor, similar to Python’s .__init__()
, and you can declare methods on this class like .print_name()
.
10:37
Just like before, you still use the keyword this
inside of the methods to get at the object instance. JavaScript also supports getters and setters. The get
and set
keywords tell JavaScript that these are special functions.
10:51 A common pattern in setters is to use an underscore variable to indicate something internal. On line 10, you’ll see the setter, and line 12, the corresponding internal variable being set.
11:03 This setter also has a side effect of printing out that it is being called. Line 15 declares the getter, which returns the internal variable. Let me load this file in.
11:22
See what happened there? Before the object was printed out by Node, there was a message. That’s the setter’s side effect. In the top area, on line 6, the Boat()
constructor assigns a value to .max_speed
.
11:35
Even in the constructor, that is calling the setter. The message "Setting speed to 120"
is being printed as a side effect of the setter being called in the constructor.
11:46
Let’s change the value of .max_speed
and see that again. And there it is. Finally, I’ll call the getter. The introduction of the class
keyword in ES6 was a great addition.
12:03 I chose my words very carefully in that previous sentence. This wasn’t really the introduction of a new thing but a wrapper for the old thing, making it easier to use.
12:12 There’s one tiny little exception, but otherwise there’s no real difference between the class and prototype declarations I showed you earlier. That tiny little difference is so tiny that it is of very little consequence unless you want to win a fight on the internet with someone who says they’re exactly the same thing. If you’d like to win such a fight, you’ll have to Google the specifics. I leave that as an exercise to the reader.
12:35 Viewer? Student? Whatever you want to call yourself.
12:41
Using the same class as before and adding the extends
keyword, you can chain prototypes to get inheritance. Here, I have a Ship
that extends my Boat
class from before.
12:52
I’m going to use Ship
to demonstrate two things: how to get at inherited methods and static methods. I’ll start with static methods. A static method in JavaScript is similar to a class method in Python.
13:05
These are methods on the class rather than on the instance object. For JavaScript, these would be on the prototype itself. Starting on line 3, I’ve declared the static method .print_fastest()
.
13:16 It takes two ships, figures out which is the quickest, and prints it out. In the lower window, I’ll load both the boat file and the ship file.
13:31
I had to load both because the ship file references the boat file. Now that they’re loaded in, let me create a Ship
.
13:42
Note the side effect from the setter in the parent Boat
is still happening. Now I’ll make another Ship
.
13:52 And with the two ships, I can call the static method.
13:59
All good. In case you’re curious about the this
keyword, it will still work in a static method, it just points to something else. Because the item in question isn’t an instance object but a class-like thing, the this
keyword will point at the class.
14:14
Okay, that’s the static method. Now some inheritance. In the Ship.print_details()
method, I want to get at a method in the parent class. I do that by using super()
.
14:25
This is similar to Python, just as a reference rather than as a method. A quick demo so you know I’m not lying, and the parent .print_name()
method gets called and Ship!
gets shouted.
14:43 One last demo before I wrap up JavaScript objects. This is what is called an Immediately Invoked Function Expression.
15:04
Take a second to parse through what is happening here. The first set of parentheses wrap a function declaration and the second set will invoke that function with the argument 33000
.
15:17
The function declaration takes a single argument called initial
. Inside the function, the mileage
variable is set and an object is returned.
15:27
This object has two functions: .get()
and .put()
. Because of the way JavaScript’s scoping rules work, the methods in this object can still get at the mileage
variable.
15:39
All this work accomplishes something very similar to objects with attributes, but in this case, the mileage
value is more hidden. If you examine the odometer
object, you’ll see the .get()
and .put()
functions, but not the mileage
. That’s what’s there printed out just after the odometer
returned, just above the prompt.
15:59
From here on in, it is like any other object. I can call .get()
, or .put()
, and .get()
again, and you can see that the value has updated.
16:15 For entertainment value, let’s do the same thing again but this time using arrow functions instead.
16:31 Take a second and compare the two declarations. I find this second one a little harder to read. It has a certain Perl line noise sort of quality to it with all the operators.
16:41
That being said, it’s definitely less typing and makes for a smaller download file. Just to prove that it’s the same, here’s the .get()
, and the .put()
, and finally, another .get()
.
16:59 Well, that was a lot, wasn’t it? Next up, I’ll start the two-part deep dive on JavaScript syntax. You’ve seen a lot of the basics. Now I’ll show you the details.
Bartosz Zaczyński RP Team on Oct. 18, 2023
@Ranudar This is a common point of confusion even for experienced developers. When you see this.max_speed
in the constructor, it’s actually invoking the setter method.
The keyword this
inside methods is generally used to access and modify the properties of the class. When you use this.max_speed = max_speed
within the constructor, it indeed appears like you’re interacting directly with the max_speed
property. But what’s really happening under the surface is that the setter for max_speed
, i.e., set max_speed(speed)
, is being invoked. So, you’re not accessing max_speed
directly. You’re triggering the setter method!
I hope this clarifies your question.
Ranudar on Oct. 19, 2023
Dear Bartosz,
Thanks a lot for the clarification! It’s great how responsive you and your RP team are!
Bartosz Zaczyński RP Team on Oct. 19, 2023
@Ranudar You’re welcome! Don’t hesitate to reach out if you have more questions 😊
Become a Member to join the conversation.
Ranudar on Oct. 17, 2023
Hi, thank you for the great course. A little question concerning the class
Boat
example. Does it matter, that there isthis.max_speed
in the constructor? It seems to me that with the getters and settersthis.max_speed
can’t be accessed. Am I missing something? Best regards!