Skip to content

JavaScript Object Creation and Prototype Chains

Posted on:August 24, 2015

There are 4 ways to create new objects in JavaScript:

  1. Object initializers, also known as literal notation
  2. Object.create
  3. Constructors
  4. ES6 classes

Depending on which method you choose, the newly created object will have a different prototype chain1.

1. Object initializers

let x = { a: 1 };
 
Object.prototype.isPrototypeOf(x); // true

Objects created in this manner will have Object.prototype as its top-level prototype:

x => Object.prototype

Arrays and functions also have their own literal notation:

let y = [1, 2, 3];
 
Array.prototype.isPrototypeOf(y); // true
Object.prototype.isPrototypeOf(Array.prototype); // true
let z = () => {}; // ES6 fat arrow syntax
 
Function.prototype.isPrototypeOf(z); // true
Object.prototype.isPrototypeOf(Function.prototype); // true

In these cases, y’s and z’s prototype chains will be

y => Array.prototype => Object.prototype

and

z => Function.prototype => Object.prototype

respectively.

2. Object.create

Object.create takes in an arbitrary object (or null) as a first argument, which will be the prototype of the new object2.

let x = {
  a: 1,
};
let y = Object.create(x);
 
y.a === 1; // true
 
x.isPrototypeOf(y); // true
Object.prototype.isPrototypeOf(x); // true

Thus, y’s prototype chain is:

y => x => Object.prototype

Object.create is actually quite special because any arbitrary object can be specified as the prototype, so we can do otherwise nonsensical things such as:

let x = [1, 2, 3];
let y = Object.create(x);
 
y.forEach; // is valid, returns function forEach()
x.isPrototypeOf(y); // true

In this case, y’s prototype chain will be:

y => x => Array.prototype => Object.prototype

3. Constructors

When a3 function Thing is invoked with the new keyword, as in let x = new Thing(), it behaves as a constructor function, which means the following things will happen:

  1. A new, empty object is created, whose prototype is Thing.prototype (the prototype object of the Thing function object)
  2. The body of the function Thing is executed, with its this set to the new empty object
  3. The return value of the Thing function is the result of the new Thing() expression, unless no return value is specified, then the new object is returned
function Thing() {}
 
let z = new Thing();
 
Thing.prototype.isPrototypeOf(z); // true

To highlight the fact that the prototype property object is distinct from the object to which it belongs to, notice the following:

Function.prototype.isPrototypeOf(Thing); // true
Object.prototype.isPrototypeOf(Thing); // true
 
Function.prototype.isPrototypeOf(Thing.prototype); // false
Object.prototype.isPrototypeOf(Thing.prototype); // true

If we think of Thing.prototype as simply an object, this shouldn’t come as a surprise. In fact, if we were do something like this:

Object.prototype.a = 1;
Function.prototype.b = 2;
z.a; // 1
z.b; // undefined

Thus, z’s prototype chain looks like:

z => Thing.prototype => Object.prototype

and not

z => Thing.prototype => Function.prototype => Object.prototype

ES6 Classes

Prototype chains in ES6 classes behave almost exactly like constructors (that is because classes are syntactic sugar around constructors):

class Thing {
  a() {
    return 1;
  }
  b() {
    return 2;
  }
}
 
class AnotherThing extends Thing {
  b() {
    return 3;
  }
  c() {
    return 4;
  }
}
 
let x = new AnotherThing();
x.c = () => {
  return 5;
};
 
x.a(); // 1
x.b(); // 3
x.c(); // 5
 
AnotherThing.prototype.isPrototypeOf(x); // true
 
Thing.prototype.isPrototypeOf(AnotherThing.prototype); // true

Thus, x’s prototype chain is:

x => AnotherThing.prototype => Thing.prototype => Object.prototype

And of course, as mentioned earlier, classes really are just syntactic sugar for constructors:

Thing.isPrototypeOf(AnotherThing); // true
 
Function.prototype.isPrototypeOf(Thing); // true
Function.prototype.isPrototypeOf(AnotherThing); // true

See footnote4 for a little more detail on how subclassing with extends actually works and how it affects the prototype chain between the subclass and the superclass.

Footnotes

  1. I’ve used isPrototypeOf here for better readability, but you can also substitute it for its inverse, __proto__, like

    x.__proto__ === Object.prototype; // true
  2. And another optional object as a second argument that specifies property descriptors.

  3. When I mean “a”, I actually mean any arbitrary function. Of course, functions meant to be used as useful constructors should look a certain way.

  4. Part of Babel’s transpiled output for extends includes a _inherits function, the full body of which is below:

    function _inherits(subClass, superClass) {
      if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError(
          "Super expression must either be null or a function, not " +
            typeof superClass
        );
      }
     
      subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
          value: subClass,
          enumerable: false,
          writable: true,
          configurable: true,
        },
      });
     
      if (superClass) subClass.__proto__ = superClass;
    }

    _inherits explicitly creates the subclass’s prototype object using Object.create, specifying the super class’s prototype as its prototype. It also sets the subclass’s __proto’s property to the superclass.