Follow that __proto__!
June 28, 2019
Programmers new to JavaScript are often confused by concepts such as callbacks, scoping rules, the number of methods/functions available to JavaScript objects,
and other features of the JavaScript language. However, it is important for programmers to understand what is going on "under the hood" in order to be an effective programmer.
In JavaScript, functions are first-class objects meaning they can have properties and methods like any other object. This has implications for how JavaScript creates
and manages certain attributes and objects using keywords such as new
or methods like Object.create()
.
At the risk of starting a programming holy war JavaScript is not a true object oriented language. However, a lot of syntactic sugar
has been added to the language so JavaScript looks and acts very much like a true object oriented language. JavaScript achieves this in large
part through the automatic creation of a __proto__
attribute whenever an object is created. The __proto__
property of an object is a reference to the class or function
from which the object is created using the keyword new
or Object.create()
. The latter overrides
the default __proto__
reference to Object.prototype
and replaces it with a reference to the object passed into create (null
can be passed as an argument as well).
When a function is invoked on an object, JavaScript looks at the object and checks to see if the function is available, and the function is executed if
it exists. If the function is not a direct attribute of the object JavaScript follows the __proto__
reference to the prototype property of the appropriate object. This is
normally the function or class from which the object is instantiated from, but programmers have the ability to change their __proto__
reference...which I don't recommend as doing so
can have unintended consequences. The __proto__
chain is traversed until the function is found or JavaScript's global object, from which all objects are inherited is reached.
The prototype
property is the default created by JavaScript, and it is stored on the object portion of a function. All JavaScript objects have a __proto__
property so there
is not one direct chain running from a given object to the global object. This should make sense using family trees as a comparison. Every person has two
parents resulting in multiple branches with every successive generation. Similarly, every JavaScript object has a __proto__
attribute (including functions because
they are also objects and objects can consist of multiple objects. The ultimate end is reached with the global Object; Object.prototype.__proto__ === null
evaluates to true
.
These concepts are fairly complicated, but they are much more understandable through the used of a simple example. Below are three different code examples which do the exact same thing. The first approach requires us to code everything, the second code example is a blend of the "old school" way of JavaScript programming and the third example is the "object oriented" way of JavaScript:
Let's walk through the JavaScript sequence when the first code example is executed:
- JavaScript assigns the function
Officer
a reference in global memory, but it does not execute the function. - JavaScript assigns the
OfficerFunctions
object a reference in global memory with two functions; str and promote. - We declare the variable
officer
and JavaScript assigns a new local execution space with its own memory. At this pointofficer
does not have a value and will not have one until the value is returned from the new context. - A new empty
Officer
object is created and thename
,rank
,branch
, anddateofRank
arguments are assigned - The invocation of
Object.create(OfficerFunctions)
causes JavaScript to create a__proto__
reference to theOffcerFunctions
object.OfficerFunctions
is also an object so it has a__proto__
reference toObject.prototype
which means any Officer object will be able to invoke functions/methods provided byObject
. Note that each object created from Officer does not get its own copy of the functions because that's not the case. The reference is a pointer toOfficerFunctions
which allows the functions to be executed by multipe Officer objects. - The command
return newOfficer
instructs JavaScript to return the value of the new object from the local execution context and assign it to the variableofficer
defined in global memory. - JavaScript executes the
promote
function on theofficer
object althoughofficer
does not have its own copy of the function. However,officer
does have a reference toOfficerFunctions
thru its__proto__
property and is thus able to execute thepromote
function. - We next run
officer.str()
which executes thestr
function via the__proto__
chain and we see that 2LT Grey Hog has been successfully promoted to 1LT! - The purpose of
console.log(officer.__proto__)
from within the Chrome Dev Tools is to show the methods available to ourofficer
object, and you can see there is yet another__proto__
object which provides even more "bonus" features and functionality available to our object courtesy of the JavaScript language.
Take a closer look at the promote function and you will soon we have an inner function named reallyPromote() defined. There's no real reason to define an inner function, but I include it is a good example of an "arrow" function using ES6 syntax. Defining an arrow function is easier (once you get used to it), but that's not the main benefit. In this case we want to take advantage of the fact that arrow functions use lexical scoping, and one of the benefits of lexical scoping is nested functions have access to variables declared in their outer scope. This is an extremely important concept because it allows us to use closures to achieve object oriented behavior such as encapsulation. A closure is the combination of a function and the lexical environment within which that function was declared. We will leave it at that as lexical scoping and closures are worthy of their own lengthy blog post. If you want to learn more I recommend an excellent MDN blog on the topic at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures. The concept is raised here as it is fundamental to how JavaScript implements its object oriented behavior which is not the same as true object oriented languages such as C++.
Let's turn our attention to the second code snippet:
The new
keyword automates a lot of code that would otherwise have to be explicitly written by a programmer including:
- Creates an object referred to as
this
in the local execution context - Creates a
__proto__
reference to the prototype property of the invoking object - Automatically returns an object to the invoking context
The concept of this
is very important and will be very familiar to those experienced with object oriented languages
such as C++. this
is an implicit reference to the current object and this
always refers to the object to the left of the dot
on which the function (method) is being called unless it is overridden by running the function using .call() or .apply().
This is JavaScript's way of allowing one object to run a function/method belonging to another object. Note - The only
difference between call() and apply() is the way arguments are passed into the function - call
expects arguments
to be separated by commas and apply
expects arguments to be passed in as an array. But I stray. Let's review the code below:
function Officer(name, rank, branch, dateOfRank) { this.name = name; this.rank = rank; this.branch = branch; this.dateOfRank = new Date(dateOfRank) } Officer.prototype.str = function() { console.log(this.rank + " " + this.name + "\nBranch: " + this.branch + "\nDate of Rank: " + this.dateOfRank ); } Officer.prototype.promote = function() { ... }
We can see our code is starting to get easier to read thanks to our use of the new
keyword. We don't have to create a new object
within the Officer function and we assign the passed in arguments to the this
object which was instantiated in the new execution
context. The str
and promote
functions are defined within the Officer
function object's (remember functions are also first class
JavaScript objects) prototype object. JavaScript automatically creates a __proto__
reference to Officer
when a new Officer
object
is created which allows us to invoke the functions defined within Officer.prototype
when we need them. By now it should be obvious, but it's
worth repeating, that JavaScript has also added a __proto__
reference within Officer.prototype
since our prototype is a unique object in its
own right. Look how much better it looks when we take advantage of JavaScript's class
keyword...now our code is starting to look like real
object oriented code!
class Officer { constructor(name, rank, branch, dateOfRank) { this.name = name; this.rank = rank; this.branch = branch; this.dateOfRank = new Date(dateOfRank) } str() { console.log(this.rank + " " + this.name + "\nBranch: " + this.branch + "\nDate of Rank: " + this.dateOfRank ); } promote() { ... } }
We have already seen the benefits afforded us by the new
keyword and things only get better when we use the class
keyword. This nice lump of syntactic sugar enables us to
define a constructor and place our str
and promote
methods (functions) within the Officer
class definition. But don't be fooled...behind the scenes JavaScript is up to its
old tricks! Let's take a look at what is going on under the hood:
If this diagram looks suspiciously like the second diagram that's because they are virtually the same...and there isn't much difference between the diagram above and the first one.
That shouldn't be a surprise since by now we know JavaScript, while providing progressive layers of abstraction, is doing the same thing when it runs the code. The class
keyword
allows us to group all methods within the class definition, and then puts them in the class prototype property which itself is an object. JavaScript adds the __proto__
property to
each object instantiated from the Officer class and we are free to remain blissfully ignorant of that fact. But if you're like me you prefer to know how things are actually working because
it makes debugging much easier. I included a snip from Chrome which shows officer.__proto__
is the same as Officer.prototype
. That doesn't mean the officer
object has a copy of the methods
defined within the Officer
class because that's not the case (officer.__proto__
is a reference). Otherwise, our program would suffer horrible performance if we instantiated tens of thousands
of officer
objects.
Hopefully you have a better understanding of the importance of an object's __proto__
property even if you never have to explicitly use it because without it JavaScript would be unable
to follow the prototype chain and provide inheritance-like capability within the language. The last code snippet introduces the object oriented features of JavaScript although we saw
there isn't much difference under the hood. The differences start to become more pronounced when programmers start to sub-class with the keyword extends
, but that is beyond the scope of this blog.