Prototypes

Every now and again I come across a snippet of JavaScript that challenges my understanding of some fundamental concept of the language. More often than not, these snippets somehow have a tendency to be related to JavaScript’s prototypical inheritance model.

I find that Prototypes in JavaScript are often misunderstood and, in my opinion, underutilised in the code that my friends and colleagues are writing on a day to day basis. Likely, the former influences the latter.

After sitting with the snippet for a little while and unpacking what was going on with it, I came to understand what was happening. The key to unlocking my understanding was remembering JavaScript’s Prototype basics.

I decided to write them down so hopefully I don’t forget again.

Note: If you’re interested, I included a break down of the snippet that prompted me to write this, it’s after The Basics and totally not necessary for you to read at all.

The Basics

Every object has a Prototype (mostly*) and every Prototype is, itself, an object.

Since every object has a Prototype, every Prototype eventually leads to one single Prototype.

Attributes belonging to objects are resolved through the Prototype Chain starting with the object itself. Essentially, this means that when foo.bar is resolved out of object foo, the interpreter first attempts to find bar in foo, if it’s not there, the interpreter will traverse the Prototype Chain looking for bar until it’s either found or there is no other Prototype to look at.

Functions can simply be attributes belonging to objects, therefore behaviour exposed by objects is just delegation through the Prototype Chain. It’s important to mentally separate what’s going on when a “method” is invoked. For instance, foo.baz() is really two separate operations to the interpreter. First, baz is resolved out of foo . Then the interpreter will invoke baz as function using foo as the context of this .

All objects of the same type share the same Prototype. An important distinction to make here is that the same Prototype is shared and not that instances of the same Prototype are used. The implication here is that since Prototypes are shared, changes which are made to a Prototype are visible to all objects which have that Prototype in their Prototype Chain.

All functions have a prototype attribute. This attribute is used as the Prototype for objects created using that function. Although all functions have this attribute, in practice the prototype is only relevant to Constructor Functions (functions which are used in conjunction with the new operator).

The Snippet

Recently, I attended Laracon US where Sean Larkin gave a talk about webpack. He did a great job presenting and got me excited to contribute to the webpack core. It was in his presentation though that I saw the snippet that ruined my day and lead me to write this post. The snippet was an example of how to write a plugin for webpack. It looked a little bit like this.

class MyFirstPlugin {
apply(complier) {
// ...
}
}

What caused me so much confusion was the apply method of this class. After some reflection, I was able to identify the source of my confusion to a combination of the following things

  1. my unfamiliarity with JavaScript’s, somewhat new, class syntax
  2. knowing that apply is a function belonging to the Function prototype object
  3. forgetting for a moment that an object Prototype and prototype are two related but very different things

When I saw that apply is the method that has to be implemented to create a webpack plugin, I thought there was some weird wiring inside the webpack core which uses this method in the way that Function.prototype.apply is used. In that, it’s invoked having compiler as the method’s this context. It was strange to me but I figured “sure why not”.

What further contributed to my confusion was the JavaScript class syntax. I knew that under the covers class MyFirstPlugin {} is the same as function MyFirstPlugin() {}. Add to that, methods defined inside a class are assigned to the prototype of the class and I thought that webpack plugins were overriding apply which was inherited fromFunction.prototype since MyFirstPlugin is a function.

I was wrong though and where I went wrong was forgetting that Prototype and prototype are not the same. What was happening was that a function called apply was being added to the prototype attribute of the function MyFirstPlugin so that instances of MyFirstPlugin will have access to it. What was not happening was that the method apply belonging to the Prototype of MyFirstPlugin was being overridden by the method apply belonging to the prototype attribute of the functionMyFirstPlugin.

JavaScript.