by Hayden Betts
What we really mean when we talk about prototypes
Beginning JavaScript devs often mistakenly use one word — “prototype” — to refer to two different concepts. But what exactly is the difference between an “object’s prototype” and the “prototype property” of JavaScript functions?
I thought I understood the concept of “prototypes” and prototypal inheritance in JavaScript. But I continued to find myself confused by references to “prototype” in code and in the documentation.
A lot of my confusion disappeared when I realized that in writing about JavaScript, people often casually use “prototype” to describe two distinct but related concepts.
- An object’s prototype: The template object from which another JavaScript object inherits methods and properties. (MDN)
- The non-enumerable
prototype
property on JavaScript functions: A convenience to facilitate a design pattern (that design pattern to be explained in-depth shortly!).
Not meaningful in itself until deliberately set to have some inheritance-related function. Most useful when used with constructor functions and factory functions (explanation coming!). Though all JS functions have the property by default. Contains aconstructor
property, which refers to the original function.
For a long time, I was comfortable with definition 1, but not with definition 2.
Why does this distinction matter?
Before I understood the difference between “an object’s prototype,” and the “non-enumerable prototype
property on functions,” I found myself confused by expressions like the following:
Array.prototype.slice.call([1, 2], 0, 1);// [ 1 ]
(sidebar: the first — but not the only — step to understanding the above is understanding call()
. Here’s a quick refresher just in case!)
A question I was not previously able to answer:
- “Why are we looking for
slice
in theArray
constructor’s prototype? Shouldn’t theArray
constructor itself contain theslice
method, and its prototype just contain some really low-level methods that all objects share?”
These questions were totally cleared up as I came to understand the design pattern that the prototype
property on Array
constructor functions exists to enable.
3 steps to understanding the prototype property on JS Functions
In order to understand theprototype
property on JS functions, you need to understand the design pattern it enables. I will build up an understanding of this pattern by first working through two less preferable alternatives.
Implementation 1: The Functional Class Pattern
Imagine we want to create a game in which we interact with dogs. We want to quickly create many dogs that have access to common methods like pet and giveTreat.
We could start to implement our game using the Functional Class Pattern as follows:
Let’s clean this up a bit by storing those methods in their own object. Then extend them inside of the createDog
factory function.
Though this implementation is easy to reason about, and conveniently reflects class-based-inheritance in other languages, it has at least one major issue: we are copying our method definitions to every dog object we create using our factory functioncreateDog
.
This takes up more memory than necessary and is not DRY. Wouldn’t it be nice if instead of copying method definitions to zeus
and casey
, we could define our methods in one place. Then have zeus
and casey
point to that place?
Refactor 1: Implementing a “Prototypal Class” Design Pattern
Prototypal inheritance gives us exactly what we asked for above. It will allow us to define our methods in one prototype object. Then have zeus
, casey
, and infinitely more objects like them point to that prototype. zeus
and casey
will then have access to all of the methods and properties of that prototype by reference.
NOTE: For the less familiar, there are many excellent tutorials out there that explain the concept of prototypal inheritance in much more depth than I do here!
A NOTE ON MY EXAMPLES BELOW: For pedagogical clarity, I use factory functions named createDog
, rather than ES5 constructor functions, to implement a prototypal model of inheritance. I choose to use factory functions because they have less ‘magic going on under the hood’ and ‘syntactic sugar’ than ES5 constructors. Hopefully, this makes it easier to stay focused on the issue at hand!
Great! Now the objects corresponding to zeus
and casey
do not themselves contain copies of the methods giveTreat
and pet
. Instead, the objects look up those methods in their prototypemethodsForShowingAffection
.
But wouldn’t it be nice if methodsForShowingAffection
were encapsulated in the createDog
factory function? Doing so would make it clear that these methods are intended for use only with that function. So a simple refactor leaves us with:
Refactor 2: Prototypal Inheritance + the prototype property on factory functions
Great! But isn’t methodsForShowingAffection
and long and strange name for a property? Why not use something more generic and predictable? It turns out that the designers of Javascript provide us with just what we are looking for. A built-in prototype
property on every function, including one on our factory functioncreateDog
.
Note that there is nothing special about this prototype
property. As shown above, we could achieve exactly the same result by setting the prototype of createDog
to a separate object called methodsForShowingAffection
. The normalcy of theprototype
property on functions in Javascript suggests its intended use-case: a convenience intended to facilitate a common design pattern. Nothing more, nothing less.
Further Reading:
For more about the prototype
property on functions in JavaScript, see ‘the function prototype’ section in this blogpost by Sebastian Porto.
The MDN article on prototypes.