Categories


Archives


Recent Posts


Categories


Tracing Javascript’s Prototype Chain

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

While part of a series on Magento 2, today’s topic is another pure javascript adventure. We’re going to run through some debugging techniques for tracing the javascript object prototype chain, the one we mentioned in our first javascript primer. Even if you think you understand this, you may want to read all the way through.

This article finally puts some of my own confusions around prototypes and javascript object inheritance to rest after over a decade of using this stuff.

The Problem

Trying to trace a javascript object’s hierarchy, or trace backwards through its prototype chain, can be difficult. That’s because

  1. Until very recently, there was no way to reflect into an object’s prototype
  2. Some of Javascript’s internal objects/functions have a property named prototype that is not that object’s javascript-prototype object.

Right now, the de-facto in-browser way of checking an object’s parent object (i.e. its prototype object) is to use the __proto__ property.

var foo = {}
console.log(foo.__proto__);

This property started life as a proprietary browser vendor implementation, but based on this callout from the Mozilla Developer Network (MDN) it sounds like __proto__ was formally added to ES6, but immediately deprecated in favor of the new (and as yet unimplemented in browsers) getPrototypeOf.

Following the ECMAScript standard, the notation someObject.[[Prototype]] is used to designate the prototype of someObject. This is equivalent to the JavaScript property proto (now deprecated). It should not be confused with the func.prototype property of functions, which instead specifies the [[Prototype]] of all instances of the given function. Since ECMAScript 6, the [[Prototype]] is accessed using the accessors Object.getPrototypeOf() and Object.setPrototypeOf().

Where things get confusing is a few of javascript’s standard objects have a property named prototype that does not always point to that object’s prototype, but can create the impression it does. Those object are Object, Array, Function, and any instance of a function object.

The first three objects are sometimes called javascript’s global objects. Other times they’re called helper objects. Still other people call them javascript’s fundamental objects. Finally, they’re also referred to as javascript’s base type constructor objects.

Today we’re going to run through what the .prototype property of each one, and by the end we hope you’ll see why all the above descriptions are, in their own way, true.

The Object Helper Object

The first object we’ll talk about is the Object object. This is a globally available object that contains helper methods for programmatically interacting with javascript’s objects.

The Object object has a property named prototype. The property contains the object that’s at the top of javascript’s prototype chain. In other words, Object.prototype is the final parent object for all objects. It is also the object that javascript assigns as the prototype to objects created via an object literal.

foo = {};
console.log( foo.__proto__ === Object.prototype);   //true

The tricky thing here? Object.prototype is not Object‘s prototype. Object is actually a grandchild of this top level object

foo = {};
console.log( Object.__proto__ === Object.prototype);           //false

console.log( Object.__proto__.__proto__ === Object.prototype); //true

The Object.prototype property is a helper property that points to the object that’s at the top of the prototype chain. It’s in no way indicative of the Object object’s place in that chain. This has, for years, created some sort of cognitive dissonance in my head whenever I start dealing with these objects, and I think I finally understand why.

This “prototype of prototypes” is very important to javascript’s object system. It’s the bedrock that the entire object system relies on. When we’re doing systems level coding that requires us to look at this object, our instincts are

we should have access to it via a global object of some kind.

We don’t. The only way to access to parent-of-all-objects is via an object that is a grandchild of that object (i.e. Object).

The fact our access comes via a property of Object (vs. a method like getAlphaPrototypeObject()) also confuses things. That it’s a property creates a strong indication (to the uninitiated) that it somehow represents a direct parent/child relationship between Object and Object.prototype.

Whether this design is the right one for client programmers is a different debate for a different time. However, for a systems developer (one who is, say, creating, debugging, or explaining a custom object system like Magento 2’s) the official APIs here are a little weird. This is often the case with systems programing, which is why good documentation and clear intentions are so important.

The Array Helper Object

The next object on our list in the Array helper object. This object provides helper methods for creating and working with javascript arrays. This object also has a property named prototype. The property does not point to Array‘s javascript-prototype

console.log(Array.__proto__  === Array.prototype);  //false

Instead, it points to the object prototype for all array objects.

foo = [];
console.log(foo.__proto__ === Array.prototype);     //true

Similar confusion to Object.prototype can happen here as well. Array is simply a helper object that provides access to the prototype object for all arrays via a .prototype property.

The Function Helper Object

Next up is the Function helper object. I bet you’ve already guessed that this object is another helper object, one with methods for creating and dealing with javascript functions. This object also has a .prototype property. However, in a switch up, Function.prototype is the Function object’s prototype.

console.log( Function.prototype === Function.__proto__ );   //true

Also, unlike Object.prototype and Array.prototype, the Function.prototype property is read only. You can’t add properties to it or replace it like you can with Array.prototype and Object.prototype.

Individual Function Objects

The last .prototype property we want to look at is the .prototype property of individual function objects.

function foo(){};
bar = function(){};
baz = new Function;

console.log(foo.prototype, bar.prototype, baz.prototype);

The .prototype property is not the function’s prototype.

console.log(foo.prototype === foo.__proto__);    //false

Instead, this .prototype property is a new object, created whenever your create a new function. At the time of creation, this .prototype object is not any object’s prototype. However, if you use this function as a constructor function (with new), the function’s .prototype property will be assigned as the new object’s prototype.

var Foo = function(){};
object = new Foo;    
console.log(object.__proto__ === Foo.prototype);     //true

This is another area where things can get super confusing. Consider the following statement — do you think this is true or false?

When you create a new object with the new Foo syntax, javascript will assign Foo‘s prototype as the new object’s prototype.

That statement is true, in that javascript will assign the property named prototype as the new object’s javascript-prototype. However javascript will not assign Foo‘s javascript-prototype (available at .__proto__) as the new object’s prototype.

I’m sure, for some of you, this is a very “wonky” distinction to make — but if you have any hope of reasoning correctly about javascript code that uses these properties, it’s also a hugely important distinction to make.

Helper Objects as Constructors

If we consider the behavior of the .prototype property of simple function objects, the .prototype properties of Object, Array, and Function suddenly become clearer. These objects, in addition to containing helper methods, are also constructor functions

o = new Object;
console.log(o.__proto__ === Object.prototype);   //true

a = new Array;
console.log(a.__proto__ === Array.prototype);    //true

f = new Function;
console.log(f.__proto__ === Function.prototype); //true

Just as it would with a userland function, javascript will look to the prototype property of these objects when they’re used as constructor objects, and use that property as the new object’s javascript prototype.

Again, when talking about these objects in a system’s programming context, it’s important to make a distinction between an object’s javascript-prototype, and a function’s .prototype property.

This is an area where a lot of well meaning tutorials can cause some confusion. For example, it’s common for code like the following

o = {};
o = new Object;

to be described (ambiguously) like this

The o variable will become an object of type Object

This isn’t exactly true. From a strict, prototype inheritance view of the universe, the o variable will have Object.prototype as its parent object. However, if you view the diagram below, you can see that instantiated objects (“All Simple Objects”) are actually higher in the hierarchy chain than Object objects.

hierarchy of javascript objects

This confusion, like a lot of javascript, can probably be traced back to its infamous 10 day creation myth, followed by years of careful, backwards compatible engineering.

The Constructor Property

This is also probably a good time to mention the .constructor property of instantiated objects. In a newly instantiated object, this property will point to the constructor function that created the object.

In other words

Foo = function(){};
object = new Foo();

console.log( object.constructor === Foo);      //true

Wrap Up

Alright! With javascript’s entrails read, our next step is a to review a few important modules and methods in Magento’s standard RequireJS library. These modules are used heavily in the uiElement object system, and it’s important we cover them now before venturing further.

Series Navigation<< Magento 2: Javascript Primer for uiElement InternalsMagento 2: uiElement Standard Library Primer >>

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 5th December 2016