Bisexual Pride

All functions in JavaScript are objects of type Function. Most Function instances (there are exceptions) have a .prototype property. However, there’s a difference between a Function instance’s .prototype property and the [[Prototype]] internal slot which is common to all objects.

The [[Prototype]] internal slot

Every object has a [[Prototype]] internal slot which refers to its prototype object. To obtain a reference to this object, you can invoke the static Object.getPrototypeOf() method.

Object.getPrototypeOf({}); // Object.prototype
Object.getPrototypeOf([]); // Array.prototype
Object.getPrototypeOf(new Date()); // Date.prototype
Object.getPrototypeOf(/hello world/); // RegExp.prototype
Object.getPrototypeOf(function () {}); // Function.prototype

The prototype chain

When you try to access a property of an object, the JavaScript interpreter will search the object’s own properties. If it can’t find the property, it will search the object’s prototype. If it still can’t find the property, it will search the prototype of the prototype. This continues until either the property is found or the end of the prototype chain is reached (Object.getPrototypeOf(Object.prototype) === null, marking the end of the chain), in which case undefined will be used.

Object.getPrototypeOf([]); // Array.prototype
Object.getPrototypeOf(Array.prototype); // Object.prototype
Object.getPrototypeOf(Object.prototype); // null

You can traverse an object’s prototype chain with the following function. You can even traverse the prototype chain of the traverse function itself! This is because it’s an object of type Function. Note that there are no parentheses after the inner traverse. This is because we want to use the reference to the Function object rather than invoke the function and use its return value.

function traverse(obj) {
let prototype = Object.getPrototypeOf(obj);

while (prototype) {
console.log(prototype);
prototype = Object.getPrototypeOf(prototype);
}

console.log(prototype);
}

traverse(traverse); // Function.prototype -> Object.prototype -> null

You can visualize the prototype chain as a linked list. In this example, the node with the Array instance as its value is the head of the list, while the node with Object.prototype as its value is the tail.

let prototypeChain = {
value: [],
next: {
value: Array.prototype,
next: {
value: Object.prototype,
next: null,
},
},
};

Example: An array’s prototype chain

Consider an array of strings that represents a few of The Avengers. An array is a special type of object with a correlation between its numbered own properties and its Array.prototype.length property.

let avengers = ["captain america", "iron man", "thor"];

When you access an element in an array by its index, you are really accessing the value of a property. That’s right: the numbered indices are just properties of the Array instance. We can prove this by invoking the static Object.keys() method.

Object.keys(avengers); // ["0", "1", "2"]

Property names have to be strings or symbols, which is why you have to use bracket notation with array indices. If you try to use dot notation, the JavaScript interpreter will throw a SyntaxError because the property name has to be a valid identifier, which cannot start with a number. Bracket notation accepts an expression, so the JavaScript interpreter is able to coerce the number inside the brackets to a string.

avengers[1]; // "iron man"

avengers[Object(1).toString()]; // what really happens is something like this

The point here is that the numbered indices are the Array instance’s own properties. When you access an element in an array by its index, the JavaScript interpeter doesn’t have to search Array.prototype, because the property exists directly on the Array instance.

However, when you try to invoke an array instance method, such as Array.prototype.forEach(), the JavaScript interpreter won’t find it among the Array instance’s own properties. It has to traverse the object’s prototype chain. This is because the method exists on the object’s prototype, i.e. Array.prototype.

Object.hasOwn(avengers, "forEach"); // false -- not an own property

"forEach" in avengers; // true -- exists in prototype chain

What if you try to access a property that doesn’t exist on Array.prototype either? The JavaScript interpreter will check the prototype of Array.prototype, which is Object.prototype (Object.getPrototypeOf(Array.prototype) === Object.prototype).

avengers.constructor; // Array

The .constructor property is inherited from Object.prototype, which is the prototype of Array.prototype. The value of the array’s .constructor property is Array, which tells us that the Array function was used to construct this object.

The typeof operator says the type of Array is "function". Although functions are a type of object—not a primitive value—the specification for the typeof operator says it should return "function" if its operand implements an internal [[Call]] method (in other words, if the object is callable, i.e. a function). It returns "object" for all other objects.

We can also check the prototype of Array, which should be Function.prototype for a Function instance. Note that we’re checking the prototype of the Array function itself, not the prototype of Array.prototype.

typeof Array; // "function"
Object.getPrototypeOf(Array); // Function.prototype

Now that we know Array is an object of type Function, we can investigate its .prototype property.

The .prototype property

If the value of the Array function’s [[Prototype]] internal slot is Function.prototype, then what is Array.prototype? What does it refer to if not the function’s own prototype?

Every Function instance which is constructible (has an internal [[Construct]] method) has a .prototype property. The value of this property is an object which is created at the same time the Function instance is created. This object becomes the prototype of the newly constructed object when the function is invoked with the new operator.

Or, as the specification for the .prototype property puts it:

Function instances that can be used as a constructor have a “prototype” property. Whenever such a Function instance is created, another ordinary object is also created and is the initial value of the function’s “prototype” property. Unless otherwise specified, the value of the “prototype” property is used to initialize the [[Prototype]] internal slot of the object created when that function is invoked as a constructor.

The prototype of the Array function is Function.prototype. This means the Array function inherits from Function.prototype, so it has access to methods like Function.prototype.bind() and Function.prototype.call().

The prototype of an Array instance is Array.prototype. This means an Array instance inherits from Array.prototype, so it has access to methods like Array.prototype.at() and Array.prototype.concat().

Object.getPrototypeOf(Array); // Function.prototype
Object.getPrototypeOf([]); // Array.prototype

Constructor functions

Let’s demonstrate this with an example. We’ll declare a Person function (an object of type Function). When invoked with the new operator, it will act as a constructor function. The newly constructed Person instance will have its own .name property.

function Person(name) {
this.name = name;
}

let ironMan = new Person("tony stark");
Object.keys(ironMan); // ["name"]

Let’s check the value of the Person function’s [[Prototype]] internal slot. It should be Function.prototype.

Object.getPrototypeOf(Person); // Function.prototype

By default, a function’s .prototype property points to an object with a single property: .constructor. The value of this property is a reference to the function itself, so that instances of the function can tell which function constructed them.

Person.prototype; // { constructor: Person }

We can add custom properties and methods to the object referenced by the .prototype property. All instances of the Person function will inherit them. Let’s add a .species property and a .greet() method.

Person.prototype.species = "human";

Person.prototype.greet = function () {
return `hi, my name is ${this.name}`;
};

The prototype of the Person function is Function.prototype, while the prototype of a Person instance is Person.prototype. A Person instance does not have a .prototype property, because it is not a Function instance, and therefore has no internal [[Construct]] method. A Person instance’s only own property is .name. It inherits its .species property and .greet() method from Person.prototype.

let ironMan = new Person("tony stark");

Object.getPrototypeOf(Person); // Function.prototype
Object.getPrototypeOf(ironMan); // Person.prototype

ironMan.prototype; // undefined

ironMan.name; // own property

ironMan.species; // inherited property
ironMan.greet(); // inherited method

Summary

All functions are objects of type Function. However, there’s a difference between a Function instance’s .prototype property and the [[Prototype]] internal slot which is common to all objects.

An object’s [[Prototype]] internal slot holds a link to its prototype object, which is the object it inherits from. You can obtain a reference to this object by invoking the static Object.getPrototypeOf() method.

A Function instance’s .prototype property refers to a different object. By default, this object has one property, .constructor, which holds a reference to the Function instance itself. You can add properties and methods to this object, and they will become available to all instances of the function when it is invoked with the new operator.