Follow that __proto__!

June 28, 2019, 5:55 p.m.


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:

  1. JavaScript assigns the function Officer a reference in global memory, but it does not execute the function.
  2. JavaScript assigns the OfficerFunctions object a reference in global memory with two functions; str and promote.
  3. We declare the variable officer and JavaScript assigns a new local execution space with its own memory. At this point officer does not have a value and will not have one until the value is returned from the new context.
  4. A new empty Officer object is created and the name, rank, branch, and dateofRank arguments are assigned
  5. The invocation of Object.create(OfficerFunctions) causes JavaScript to create a __proto__ reference to the OffcerFunctions object. OfficerFunctions is also an object so it has a __proto__ reference to Object.prototype which means any Officer object will be able to invoke functions/methods provided by Object. 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 to OfficerFunctions which allows the functions to be executed by multipe Officer objects.
  6. The command return newOfficer instructs JavaScript to return the value of the new object from the local execution context and assign it to the variable officer defined in global memory.
  7. JavaScript executes the promote function on the officer object although officer does not have its own copy of the function. However, officer does have a reference to OfficerFunctions thru its __proto__ property and is thus able to execute the promote function.
  8. We next run officer.str() which executes the str function via the __proto__ chain and we see that 2LT Grey Hog has been successfully promoted to 1LT!
  9. The purpose of console.log(officer.__proto__) from within the Chrome Dev Tools is to show the methods available to our officer 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:

  1. Creates an object referred to as this in the local execution context
  2. Creates a __proto__ reference to the prototype property of the invoking object
  3. 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.

Comment Enter a new comment: