JavaScript is a special language. It was designed in a hurry in its early days;
it is an embedded scripting language — the language itself doesn’t even have a
standard module system or standard I/O constructs. On the other hand, it
supports both the object-oriented programming and functional programming
paradigms, is the only language reliably available in web browsers, and got
hugely popular over the last several years.
The unusual thing about JavaScript is that, unlike most if not all of the other
currently-popular OOP-friendly languages, JavaScript uses prototype-based
inheritance, instead of class-based (“classical”) inheritance. JavaScript’s
this and new keywords also mean completely different things as compared to
the same keywords in, say, Java. For me, when I started learning JavaScript,
these were the hardest parts of JavaScript, so I decided to write this article
to explain how JavaScript’s prototypes, this, and new works.
I’m assuming you are a programmer and know a classical OOP language like Java,
C++, or Python. The version of JavaScript I’m using is ECMAScript 5;
however, I am mostly using NodeJS v5.6.0 to check the validity of my examples.
First, in case you haven’t used JavaScript much, let’s look at a few things you
can (or can’t) do in JavaScript.
Prototypes
Ok, so you’ve already heard about JavaScript’s “prototype chain”, and how name
look-up traverses the chain. But how does everything work in concrete terms?
Here are a few prototype-related phrases or keywords:
“The [[Prototype]] property”, e.g. as used in the ES5 spec;
the prototype property on constructor functions, where
“constructor functions” are those functions written to be used with new.
It turns out that among the above, [[Prototype]] and __proto__ actually
refer to the same concept — the “prototype” of an object, where you can try to
look properties up in case the object itself doesn’t have that property. The
prototype property (of constructor functions, usually), on the other hand, is
not the “prototype” of the object holding that property.
We say an object “has a property” if that property can be found in that object
or anywhere on that object’s prototype chain. We say an object “has an own
property” if we can find that property without traversing the object’s prototype
chain.
To check whether a given object “has a property”, we can use in:
To check whether a given object “has an own property”, we can use
.hasOwnProperty:
As an aside, .hasOwnProperty is available on a fresh, default object because
hasOwnProperty is actually a property of Object.prototype, which the
prototypes of all usual objects are set to.
Since ES5, there are a few nice “meta-programming” functions that allow us to do
things with the prototypes of objects:
Object.create
Object.getPrototypeOf
Object.setPrototypeOf
See these functions in action here:
Constructor functions
Constructor functions are functions whose author wrote them with the intention
that the function is called using new. For now, let’s worry so much about what
exactly new does, but focus on the constructor function and the object it
“constructs”.
How does JavaScript work out that t is an “instance” of Thing? (Whatever
“instance” means in a class-less, prototype-based language.) It turns out what
instanceof does is to traverse the prototype chain.
As seen from the above examples, it seems like the prototype property on
constructor functions is more like some sort of meta-data that the mechanism for
new reads off and Does The Right Thing with. If we put some properties into
Thing.prototype, then all Things will be able to see that property.
One cool thing is since everything is dynamic, even if we retroactively add new
properties into the prototype property of a constructor function, objects that
we’ve already created beforehand using that constructor function can also access
the new property.
We can also add properties that are functions to Thing.prototype to achieve
very classical-OOP-reminiscient effects:
But of course we’ve jumped the gun — we still don’t know what this does yet.
So let’s move on to studying this.
The “this” keyword
If you know about the special arguments object available inside function
definitions, explaining this would be a bit easier.
The thing to pay attention to here is that the value that is stored in the
arguments object inside the body of a function depends on how the function is
called. If the function is called with two arguments, then arguments will hold
two things. If the function is called without any arguments, then
arguments.length will be 0.
Think of this as another special variable like arguments which:
is available inside the body of a function, and
holds a value that is dependent on how the function is called.
Strictly speaking, the this keyword holds a value even if it is accessed from
outside any functions, and there are weird, stupid things like how it sometimes
refers to the global object, depending on whether “strict mode” is active for
that context; for now, let’s focus on using this inside the body of function
definitions, without strict mode.
The thing about this is that its value, instead of depending on the arguments
that are passed to the function when the function is called, depends on the
syntax of the function call expression. I know, it’s weird, right? It makes me
think a little of dynamically-scoped languages, where what a variable name is
not looked-up where the function is defined, but rather where it is called. Some
would call it “very late binding”, where this is not bound even at
run-time, but only bound at call-time. Anyway, let’s see some examples.
Similar to how we have Object.create, Object.getPrototypeOf etc. for dealing
with prototypes, we have the following functions to deal with this:
Function.prototype.bind
Function.prototype.call
Function.prototype.apply
These functions basically let you explicity specify which object to pass as
this to a given function.
I’ll leave you to read the docs on MDN for the details of, or difference
between, call and apply. I’ll also just mention that in ECMAScript 6,
Arrow Functions also gives new ways of dealing with the this
parameter.
By now, we can understand why the example from the very first snippet of this
post of pushing elements into an array — where we were trying to pass a method
of an object as a function to a higher-order function — didn’t work as naively
expected.
Oops! It turns out that Array.prototype.forEach passes more than
just one argument to its argument function (Array.prototype.push in
this case), so .push pushed all of those arguments in. For details,
read the docs for Array.prototype.forEach.
The “new” keyword
Now that we know how this works, and how prototypes can be set up, we are
equipped to learn about the mechanism for new! What does the new keyword do?
Well, we can just read section 13.2.2 of the ES5 spec. (I found this
when reading StackOverflow about returning values from constructor
functions.) I personally think of new as just syntactic sugar,
since its behaviour can be implemented in JavaScript itself. Before we dive deep
into the quirks and details of new as per the spec, here’s overly-simple
implementation of new as a JavaScript function, using constructs that we’ve
learnt so far, that does what we’ve seen new do so far:
This implementation already does a lot of what new does. See it in action:
Although the above implementation is about right, it doesn’t do all of what
the spec specifies. There are many weird, corner cases of how new might be
used that we haven’t covered. Providing A Return Value In A JavaScript
Constructor by Ben Nadel demonstrates many of such cases. So
let’s read the spec now, and see how to implement a basically-compliant new.
Go read the spec now: 13.2.2 [[Construct]]. Here’s what that
section says new should do, implemented as a function:
Basically, as compared to the overly-simple implementation, this
basically-compliant implementation takes care of the case where the constructor
function’s prototype property holds a non-object value (which it usually
doesn’t, because by default it’s effectively Object.create(Object.prototype)),
or where the constructor function returns an object value (which is usually
doesn’t, because it doesn’t need to return anything).
Read the spec or the code above slowly. Have a think through — this is all
there is to new.
Simulating classical OOP
Classical OOP wants the following things:
Encapsulation
Inheritance
Polymorphism
Although there are no notion of “private” object members in JavaScript, by using
closures cleverly, we can achieve that effect. Since closures are not a
JavaScript-specific thing, I won’t explain it in this post. For some patterns of
using closures in the context of JavaScript, go read Douglas Crockford’s book
JavaScript: The Good Parts.
Polymorphism is obvious. Everything is dynamic, so the dispatch of method-calls
is dynamic. Duck typing. Yada yada. Easy.
The not-so-obvious one of the three OOP things is inheritance — because, well,
JavaScript doesn’t have classes. Here’s how we achieve similar outcomes using
prototypes and constructor functions:
I’m quite new to JavaScript, so I don’t know what way of using the
prototype-related building blocks to simulate classical OOP is more idiomatic.
For instance, some people seem to prefer something like
… instead of the
… which I used. My reason for doing what I did is that at time of defining the
SubThing “class”, I may not have enough information to instantiate a Thing,
depending on what arguments the Thing constructor takes.
The point here is, although the above code sample may not be idiomatic, it
nonetheless certainly can get the “simulate classical OOP” job done, nicely,
using JavaScript’s prototype mechanisms.
The end
So there you have it — an explanation of what I think is JavaScript’s hardest
parts: prototypes, this, and new. Equipped with this knowledge, you are
ready to learn all the other parts of JavaScript, like ES6 classes (which are
allegedly still prototype-based) or ES6 arrow functions (which makes this
different). You are now JavaScript-ready!