JavaScript's new keyword, under the hood 🐬
Or, how classes are implemented in terms of prototypes
This is part of a series on the new
keyword in various programming languages.
JavaScript is a wonderful general programming language. It provides a lot of features that allows you to write imperative, functional, and object-oriented code. However, JS’s object oriented features are implemented in a fairly different way to other languages such as Java and C++, which has led to confusion over the years. This confusion has also created many bugs and misunderstanding around JS’s legitimacy as a language.
A key point to start from, is that object-oriented does not mean “classes and inheritance” — they’re just a common way to achieve it. As such, Javascript does not have classes, no matter what ES6 looks like. Javascript has prototypes, and “class-like” prototype syntax.
This is not a beginner’s guide to using new
— it is a deeper dive into lower level building blocks that make JavaScript what it is.
If you are coming from C++, Java, C# or any other language that has a new
keyword, be aware that each language handles new
in its own unique way — and prior knowledge of another language may actually confuse what’s happening in JavaScript!
At a glance 👓
- If you want object oriented code, use the Class keyword
- Javascript’s Class syntax is actually hiding prototypical inheritance
- Functions are secretly objects
- Prototypes save us memory
- The
new
keyword makes a function call act in a constructor-like manner
Follow the code
Follow along at https://codesandbox.io/s/javascript-new-keyword-animated-bouncing-balls-nu43i
Setting the scene 💺
Our boss at the local niche hardware store, “Shovels and Chairs” has asked us to create a web-based bouncing ball simulation. It’s for an advertising campaign, and we need to impress potential customers. As such, we’ve got a requirement that we can display 1,000 balls bouncing on a screen at the same time — so we’re going to need to be somewhat efficient.
This is cool. We can imagine it in memory as such:
However, there’s only one ball here, and we want to make many.
Let’s extract a makeBall
construction function that will give us a new Ball object. We’ll also roll in the update
function so we can call ball.update(delta)
rather than updateBall(ball, delta)
.
(This means that in good OOP encapsulated style, we’re only fiddling with ball
’s members in private, not public.)
Inside makeBall
, we create a ball
object, and after creation add a new update
function to it.
How does this look?
Functions are actually objects 🐬🐳
Just like how all dolphins are actually whales, it’s worth noting at this point that all functions are ac tually objects. This has many implications, most of which we’re not interested in today — but it does mean that every time you write function
or () => { }
, you’re creating a new object with the function code in it. This is different to C++ and Java, where the code for each function only exists once in the compiled assembly.
That means, if we created 3 balls, our memory layout would look like this:
That’s a lot of wasted memory to store the update functions, which are identical across them all. Our boss certainly isn’t going to splash out for more RAM to display this, and our customers are getting antsy to see what technical marvel they’ve been promised.
Is there a way we can still couple the data and code closely, but only store the update code in one location?
Let’s bring in this and new
We’re going to rewrite the code to make use of this
and new
— note that we don’t explicitly declare this
anywhere. The new
keyword will provide it for us.
Essentially, we’re replacing any reference to ball
with the word this
.
In this new snippet, we’ve renamed the makeBall
function to Ball
, replaced references to ball
with this
inside it, and on line 24, added the new
keyword before calling the function.
We haven’t actually declared the this
variable at this point, which means calling this function will actually throw an error now (if we didn’t return on line 4!) We’ll see where this variable gets set later.
Right now, there is no obvious advantage to having done this. It’s fairly equivalent to the previous code.
So why would we have made this change? What new power has it given us?
Enter the prototype 🤸♂️
In C++, Java, C#, etc, all functions exist once in the compiled code — what if we attempted to do a similar thing in JS?
In JS, all objects have a hidden value internally called prototype
which points to another object. Yes, all objects. Even an empty object like {}
.
You can see this is in some browsers like Chrome by typing Object.getPrototypeOf({})
into your console.
As we said above, all functions are objects, which means all functions must have a prototype
value on it. Let’s rework the code just a little bit:
We’ve extracted the update
function and moved it onto the prototype.
But what is the prototype, and why is this in any way helpful?
Before I explain what the new
keyword does and how it relates to prototypes, let’s take a look at what the memory layout for our balls
array is now:
There’s now only one function object for update
. We could call new Ball(100)
a million times, and there’d still only be one instance of the update function object. Each of these instances would point at the same Ball.prototype
, which points at the one update
function.
So what’s happening here? How was the prototype
set?
What the new keyword is really doing 🔮
From the MDN description, the new
keyword:
- Creates an empty JavaScript object (like
{}
) - Replaces the prototype of this object to the newed function’s prototype — remember, functions are objects, and all objects already have a prototype.
- Passes the newly created object from Step 1 as the
this
variable. (This happens behind the scenes.) - Returns
this
if the function doesn't return its own object.
You can actually model this inside JavaScript (though please don’t actually use this code anywhere!)
Weird caveat with arrow functions
You might have noticed that I’ve only use function () { }
so far in this post, and never () => {}
. In most cases, arrow functions are the same as regular functions, however there’s something worth noting.
An arrow function cannot be used with the new
keyword. You’ll see the following error if you try to do so:
This is likely to discourage use of making function constructors directly, and to prefer use of the class
keyword.
So what about classes? 🏫
Modern JavaScript (ES6, or languages like Typescript) provides the class
keyword to simplify some of this process for us.
Let’s rewrite Ball using class syntax.
This is the modern, recommended way to write the Ball class. While I wouldn’t generally recommend you use JavaScript in a class heavy way, if you’re going to, you may as well follow best practice.
If you’re in a modern web browser or version of Node (since 4.3!), the class
keyword is understood and supported directly. By following the intended semantics, in theory, it can also produce more optimized code under the hood (though I’ve yet to see any proof of this being taken advantage of.)
Why is the class keyword any better than writing it myself?
It’s better in the same way that writing C is generally considered better than writing direct Assembly code. The higher abstraction of the class
keyword can wrap up some messy bits and make it harder for us to make mistakes. It helps us fall into the pit of success.
Note that if you’re targeting ES5 (“regular” JavaScript), you’ll need to use something like Babel to compile your new code down. If you run this example through Babel, you’ll see how it actually generates code similar (but at first glance, quite different) to our first examples.
The class keyword is still creating a prototype based object.
You can see this here.
To conclude ✔️
Our marketing campaign was a success! Customers love watching 1000s of balls bounce along a web-page without having to shell out for extra RAM.
Unfortunately, due to a combination of the the campaign having nothing to do with our products or services, and funds being embezzled by the CEO, the hardware store tanked and shut down in a matter of hours.
To actually conclude
JavaScript provides powerful object-oriented features, but we should use them with intent and understanding. JavaScript provides many powerful functional features as well, which are well worth looking into.
If you’re coming to JavaScript from another language, remember to write idiomatic JavaScript code — not to try shoehorning C++ concepts like inheritance chains in because that’s what you’re used to.
Further reading
Check out the following three articles for further, deeper understanding of JavaScript objects and prototypes.