[[Prototype]]

In programming, we often want to take something and extend it. For instance, we have a user object with its properties and methods, and want to make admin and guest as slightly modified variants of it. We’d like to reuse what we have in user, not copy/reimplement its methods, just build a new object on top of it.

Prototypal inheritance is a language feature that helps in that. In JavaScript, objects have a special hidden property [[Prototype]] (as named in the specification), that is either null or references another object. That object is called “a prototype”:

That [[Prototype]] has a “magical” meaning. When we want to read a property from object, and it’s missing, JavaScript automatically takes it from the prototype. In programming, such thing is called “prototypal inheritance”. Many cool language features and programming techniques are based on it.

The property [[Prototype]] is internal and hidden, but there are many ways to set it. One of them is to use __proto__, like this:

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal;

Please note that __proto__ is not the same as [[Prototype]]. That’s a getter/setter for it. We’ll talk about other ways of setting it later, but for now __proto__ will do just fine.

If we look for a property in rabbit, and it’s missing, JavaScript automatically takes it from animal.

// we can find both properties in rabbit now:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

Here we can say that "animal is the prototype of rabbit" or "rabbit prototypally inherits from animal". So if animal has a lot of useful properties and methods, then they become automatically available in rabbit. Such properties are called “inherited”. If we have a method in animal, it can be called on rabbit:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// walk is taken from the prototype
rabbit.walk(); // Animal walk

The method is automatically taken from the prototype, like this:

The prototype chain can be longer:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)

There are actually only two limitations:

Also it may be obvious, but still: there can be only one [[Prototype]]. An object may not inherit from two others.

Read/write rules

Reading Data Properties

If a data property doesn't exists, it is looked up in the prototype chain.

let user = {
  firstName : "Santanu",
  lastName : 'Bera',
  get fullName(){
    return this.firstName + " " + this.lastName;
  }
}
let Admin = {
  __proto__ : user,
  isAdmin : true
}
console.log(Admin.firstName); // Santanu

Writting/Deleting a Data Property

If you do delete or write operation, it is done directly on the object not in the prototype chain.

let user = {
  firstName : "Santanu",
  lastName : 'Bera',
  get fullName(){
    return this.firstName + " " + this.lastName;
  }
}
let Admin = {
  __proto__ : user,
  isAdmin : true
}
console.log(Object.keys(Admin));
Admin.firstName = "Hello";
console.log(Object.keys(Admin));

// Output -
[ "isAdmin" ]
[ "isAdmin", "firstName" ]

In this example, we assign a firstName to the Admin object. So, Admin object gets a new property firstName even though the prototype chain already has firstName. And from now if you access the property firstName, it is taken directly from the Admin object itself.

Similarly, in the following example, we are trying to delete firstName, but the delete operation will not succeed as it looks for the property in the Admin object and there is no firstName property in the Admin object.

let user = {
  firstName : "Santanu",
  lastName : 'Bera',
  get fullName(){
    return this.firstName + " " + this.lastName;
  }
}
let Admin = {
  __proto__ : user,
  isAdmin : true
}
console.log(Object.keys(Admin));
delete Admin.firstName;
console.log(Object.keys(Admin));

// Output -
[ "isAdmin" ]
[ "isAdmin" ]

Writting Accessor Properties

For writting accessor properties, the behavior is somehow different. In the following example, the writting operation is done directly on the prototype chain. But the Admin property gets it's own new property firstName and lastName.

let user = {
  firstName : "Santanu",
  lastName : 'Bera',
  get fullName(){
    return this.firstName + " " + this.lastName;
  },
  set fullName(name){
    [this.firstName, this.lastName] = name.split(" ");
  }
}
let Admin = {
  __proto__ : user,
  isAdmin : true
}
console.log(Object.keys(Admin)); // [ "isAdmin" ]
Admin.fullName = "Lorem Ipsum";
console.log(Object.keys(Admin)); // [ "isAdmin", "firstName", "lastName" ]
console.log(Object.keys(user)); // [ "firstName", "lastName", "fullName" ]
console.log(Admin.fullName); // Lorem Ipsum
console.log(user.fullName); // Santanu Bera

This is because the accessor properties acts like a method, they are not regular properties. They directly act on the object.

When we assign fullName to the Admin object, it tries to find it in the Admin object, in our case it is not in the Admin. So it looks in the prototype chain. When it finds that it is a accessor properties, it calls its setter to act on the Admin object. If it were a data property then a new property fullName would get created in the Admin object.

Here when you assign fullName to the Admin object, the [this.firstName, this.lastName] = name.split(" "); gets executed. And new property firstName and lastName gets created on the Admin object for its own.

Reading accessor properties

let user = {
  firstName : "Santanu",
  lastName : 'Bera',
  get fullName(){
    return this.firstName + " " + this.lastName;
  },
  set fullName(name){
    [this.firstName, this.lastName] = name.split(" ");
  }
}
let Admin = {
  __proto__ : user,
  isAdmin : true
}
console.log(Admin.fullName); // Santanu Bera

When we read property fullName, it tries to find it in the Admin object. In our case it is not in the Admin object. So it tries to find it in the prototype chain, when it finds out that it is an accessor property, it runs its getter to get the result. The getter run on the Admin object. Now getter uses this.firstName and this.lastName. Here this refers to Admin. But there is no Admin.firstName and Admin.lastName. So, it takes the value from the prototype chain and returns it.

Now when you assign Admin.fullName = "Some Name"; it run its setter, and new property firstName and lastName gets created on the Admin object. Now next time you read Admin.fullName, it takes the value from the Admin object itself.

The value of “this”

An interesting question may arise in the example above: what’s the value of this inside set fullName(value)? Where the properties this.name and this.surname are written: user or admin?

The answer is simple: this is not affected by prototypes at all. No matter where the method is found: in an object or its prototype. In a method call, this is always the object before the dot.

So if a accessor properties or an object method uses this, it always refers to the object from which it is called. Not the object that is inherited from. This gives us ability to work with the object itself, not it's base object. That keeps the base object seperated from the inherited/derived object. We can manipulate the derived object while the base object stays intact in any manner.

In our example, we cannot change or manipulate anything on the user object with the Admin object calls.

F.prototype

In modern JavaScript we can set a prototype using __proto__.

But in the old times, there was another (and the only) way to set it: to use a "prototype" property of the constructor function. And there are still many scripts that use it.

As we know already, new F() creates a new object. When a new object is created with new F(), the object’s [[Prototype]] is set to F.prototype.

In other words, if F has a prototype property with a value of the object type, then new operator uses it to set [[Prototype]] for the new object.

let animal = {
  eats: true
};

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

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

Setting Rabbit.prototype = animal literally states the following: "When a new Rabbit is created, assign its [[Prototype]] to animal".

Here is another example -

let user = {
  firstName : "Santanu",
  lastName : "Bera",
};
function Admin(id){
  this.id = id;
};
Admin.prototype = user;
let admin = new Admin(100);
console.log(admin.firstName); // Santanu

The inheritance mechanism is same but it just an old way to set the base object from which the object is inherited from.

Default F.prototype, constructor property

Every function has the "prototype" property even if we don’t supply it. The default "prototype" is an object with the only property constructor that points back to the function itself.

function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/

We can check it:

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // true

Naturally, if we do nothing, the constructor property is available to all rabbits through [[Prototype]]:

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // true (from prototype)

We can use constructor property to create a new object using the same constructor as the existing one.

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");

To summurize,

rabbit.constructor == Rabbit.prototype.constructor == Rabbit

That’s handy when we have an object, don’t know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind. But probably the most important thing about constructor is that…

…JavaScript itself does not ensure the right "constructor" value.

Yes, it exists in the default "prototype" for functions, but that’s all. What happens with it later – is totally on us. In particular, if we replace the default prototype as a whole, then there will be no "constructor" in it.

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false

So, to keep the right "constructor" we can choose to add/remove properties to the default "prototype" instead of overwriting it as a whole:

function Rabbit() {}

// Not overwrite Rabbit.prototype totally
// just add to it
Rabbit.prototype.jumps = true
// the default Rabbit.prototype.constructor is preserved

Or, alternatively, recreate the constructor property manually:

Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// now constructor is also correct, because we added it

Hiding Properties

You can use object.constructor property to keep sensitive information as they are not accessible from for..in or Object.keys().

let user = {
  firstName : "Santanu",
  lastName : "Bera",
};
function Admin(id){
  this.id = id;
};
// Admin.prototype = user;
let admin = new Admin(100);
admin.constructor.amount = 1000;
console.log(admin.constructor.amount); // 1000
console.log(Object.keys(admin)); // [ "id" ]
for(key in user){
  console.log(key); // firstName, lastName
}

Native prototypes

The "prototype" property is widely used by the core of JavaScript itself. All built-in constructor functions use it. Let’s say we output an empty object:

let obj = {};
alert( obj ); // "[object Object]" ?

Where’s the code that generates the string "[object Object]"? That’s a built-in toString method, but where is it? The obj is empty!

…But the short notation obj = {} is the same as obj = new Object(), where Object – is a built-in object constructor function. And that function has Object.prototype that references a huge object with toString and other functions.

When new Object() is called (or a literal object {...} is created), the [[Prototype]] of it is set to Object.prototype by the rule that we’ve discussed in the previous chapter:

Afterwards when obj.toString() is called – the method is taken from Object.prototype. You can check it like this -

let obj = {};

alert(obj.__proto__ === Object.prototype); // true
// obj.toString === obj.__proto__.toString == Object.prototype.toString

Please note that there is no additional [[Prototype]] in the chain above Object.prototype -

alert(Object.prototype.__proto__); // null

Other built-in prototypes

Other built-in objects such as Array, Date, Function and others also keep methods in prototypes.

For instance, when we create an array [1, 2, 3], the default new Array() constructor is used internally. So the array data is written into the new object, and Array.prototype becomes its prototype and provides methods. That’s very memory-efficient. By specification, all built-in prototypes have Object.prototype on the top. Sometimes people say that “everything inherits from objects”.

let arr = [1, 2, 3];

// it inherits from Array.prototype
alert( arr.__proto__ === Array.prototype ); // true

// then from Object.prototype
alert( arr.__proto__.__proto__ === Object.prototype ); // true

// and null on the top.
alert( arr.__proto__.__proto__.__proto__ ); // null

Some methods in prototypes may overlap, for instance, Array.prototype has its own toString that lists comma-delimited elements:

let arr = [1, 2, 3]
alert(arr); // 1,2,3 <-- the result of Array.prototype.toString

As we’ve seen before, Object.prototype has toString as well, but Array.prototype is closer in the chain, so the array variant is used.

In-browser tools like Chrome developer console also show inheritance (may need to use console.dir for built-in objects):

Other built-in objects also work the same way. Even functions. They are objects of a built-in Function constructor, and their methods: call/apply and others are taken from Function.prototype. Functions have their own toString too.

function f() {}

alert(f.__proto__ == Function.prototype); // true
alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects

Native Prototype for Primitives

The most intricate thing happens with strings, numbers and booleans. As we remember, they are not objects. But if we try to access their properties, then temporary wrapper objects are created using built-in constructors String, Number, Boolean, they provide the methods and disappear.

These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as String.prototype, Number.prototype and Boolean.prototype.

Values null and undefined have no object wrappers

Special values null and undefined stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes too.

Changing native prototypes

Native prototypes can be modified. For instance, if we add a method to String.prototype, it becomes available to all strings:

String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

During the process of development we may have ideas which new built-in methods we’d like to have. And there may be a slight temptation to add them to native prototypes. But that is generally a bad idea.

Prototypes are global, so it’s easy to get a conflict. If two libraries add a method String.prototype.show, then one of them overwrites the other one.

In modern programming, there is only one case when modifying native prototypes is approved. That’s polyfills. In other words, if there’s a method in JavaScript specification that is not yet supported by our JavaScript engine (or any of those that we want to support), then may implement it manually and populate the built-in prototype with it. So polyfills code always modify the Native Prototype. Here is an example of a polyfill -

if (!String.prototype.repeat) { // if there's no such method
  // add it to the prototype

  String.prototype.repeat = function(n) {
    // repeat the string n times

    // actually, the code should be more complex than that,
    // throw errors for negative values of "n"
    // the full algorithm is in the specification
    return new Array(n + 1).join(this);
  };
}

alert( "La".repeat(3) ); // LaLaLa

Borrowing from prototypes

In the chapter Decorators and forwarding, call/apply we talked about method borrowing:

function showArgs() {
  // borrow join from array and call in the context of arguments
  alert( [].join.call(arguments, " - ") );
}

showArgs("John", "Pete", "Alice"); // John - Pete - Alice

Because join resides in Array.prototype, we can call it from there directly and rewrite it as:

function showArgs() {
  alert( Array.prototype.join.call(arguments, " - ") );
}

That’s more efficient, because it avoids the creation of an extra array object []. On the other hand, it is longer to write.

Methods for prototypes

Let's cover additional methods to work with a prototype. There are also other ways to get/set a prototype, besides those that we already know:

let animal = {
  eats: true
};

// create a new object with animal as a prototype
let rabbit = Object.create(animal);

alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit

Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}

Object.create has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:

let animal = {
  eats: true
};

let rabbit = Object.create(animal, {
  jumps: {
    value: true
  }
});

alert(rabbit.jumps); // true

We can use Object.create to perform an object cloning more powerful than copying properties in for..in:

// fully identical shallow clone of obj
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

This call makes a truly exact copy of obj, including all properties: enumerable and non-enumerable, data properties and setters/getters – everything, and with the right [[Prototype]].

__proto__ as a key

As we know, objects can be used as associative arrays to store key/value pairs. …But if we try to store user-provided keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except "__proto__".

let obj = {};

let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";

alert(obj[key]); // [object Object], not "some value"!

Here if the user types in __proto__, the assignment is ignored! That shouldn’t surprise us. The __proto__ property is special: it must be either an object or null, a string can not become a prototype.

The __proto__ is not a property of an object, but an accessor property of Object.prototype:

So, if obj.__proto__ is read or assigned, the corresponding getter/setter is called from its prototype, and it gets/sets [[Prototype]].

As it was said in the beginning: __proto__ is a way to access [[Prototype]], it is not [[Prototype]] itself. Now, if we want to use an object as an associative array, we can do it with a little trick:

let obj = Object.create(null);

let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";

alert(obj[key]); // "some value"

Object.create(null) creates an empty object without a prototype ([[Prototype]] is null):

So, there is no inherited getter/setter for __proto__. Now it is processed as a regular data property, so the example above works right.

We can call such object “very plain” or “pure dictionary objects”, because they are even simpler than regular plain object {...}.

A downside is that such objects lack any built-in object methods, e.g. toString:

let obj = Object.create(null);

alert(obj); // Error (no toString)

Please note that most object-related methods are Object.something(...), like Object.keys(obj) – they are not in the prototype, so they will keep working on such objects:

let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";

alert(Object.keys(chineseDictionary)); // hello,bye

Getting all properties

There are many ways to get keys/values from an object. We already know these ones:

If we want symbolic properties:

If we want non-enumerable properties:

If we want all properties:

These methods are a bit different about which properties they return, but all of them operate on the object itself. Properties from the prototype are not listed.

The for..in loop is different: it loops over inherited properties too.

let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// only own keys
alert(Object.keys(rabbit)); // jumps

// inherited keys too
for(let prop in rabbit) alert(prop); // jumps, then eats

If we want to distinguish inherited properties, there’s a built-in method obj.hasOwnProperty(key): it returns true if obj has its own (not inherited) property named key.

So we can filter out inherited properties (or do something else with them):

let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);
  alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false
}

Here we have the following inheritance chain: rabbit, then animal, then Object.prototype (because animal is a literal object {...}, so it’s by default), and then null above it:

Note, there’s one funny thing. Where is the method rabbit.hasOwnProperty coming from? Looking at the chain we can see that the method is provided by Object.prototype.hasOwnProperty. In other words, it’s inherited.

…But why hasOwnProperty does not appear in for..in loop, if it lists all inherited properties? The answer is simple: it’s not enumerable. Just like all other properties of Object.prototype. That’s why they are not listed.