Prototype methods, objects without __proto__

In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.

The __proto__ is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).

The modern methods are:

These should be used instead of __proto__.

For instance:

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

The descriptors are in the same format as described in the chapter Property flags and descriptors.

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]].

Brief history

If we count all the ways to manage [[Prototype]], there’s a lot! Many ways to do the same!

Why so?

That’s for historical reasons.

  • The "prototype" property of a constructor function works since very ancient times.
  • Later in the year 2012: Object.create appeared in the standard. It allowed to create objects with the given prototype, but did not allow to get/set it. So browsers implemented non-standard __proto__ accessor that allowed to get/set a prototype at any time.
  • Later in the year 2015: Object.setPrototypeOf and Object.getPrototypeOf were added to the standard, to perform the same functionality as __proto__. As __proto__ was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is optional for non-browser environments.

As of now we have all these ways at our disposal.

Why was __proto__ replaced by the functions getPrototypeOf/setPrototypeOf? That’s an interesting question, requiring us to understand why __proto__ is bad. Read on to get the answer.

Don’t reset [[Prototype]] unless the speed doesn’t matter

Technically, we can get/set [[Prototype]] at any time. But usually we only set it once at the object creation time, and then do not modify: rabbit inherits from animal, and that is not going to change.

And JavaScript engines are highly optimized to that. Changing a prototype “on-the-fly” with Object.setPrototypeOf or obj.__proto__= is a very slow operation, it breaks internal optimizations for object property access operations. So evade it unless you know what you’re doing, or JavaScript speed totally doesn’t matter for you.

“Very plain” objects

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__".

Check out the example:

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.

But we didn’t intend to implement such behavior, right? We want to store key/value pairs, and the key named "__proto__" was not properly saved. So that’s a bug!

Here the consequences are not terrible. But in other cases, we may be assigning object values, then the prototype may indeed be changed. As the result, the execution will go wrong in totally unexpected ways.

What’s worst – usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side.

Unexpected things also may happen when accessing toString property – that’s a function by default, and other built-in properties.

How to evade the problem?

First, we can just switch to using Map, then everything’s fine.

But Object also can serve us well here, because language creators gave a thought to that problem long ago.

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

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

As it was said in the beginning of this tutorial section: __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)

…But that’s usually fine for associative arrays.

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 = "你好";
chineseDictionary.bye = "再见";

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

Summary

Modern methods to setup and directly access the prototype are:

The built-in __proto__ getter/setter is unsafe if we’d want to put user-generated keys in to an object. Just because a user may enter “proto” as the key, and there’ll be an error with hopefully easy, but generally unpredictable consequences.

So we can either use Object.create(null) to create a “very plain” object without __proto__, or stick to Map objects for that.

Also, Object.create provides an easy way to shallow-copy an object with all descriptors:

let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

We also made it clear that __proto__ is a getter/setter for [[Prototype]] and resides in Object.prototype, just as other methods.

We can create an object without a prototype by Object.create(null). Such objects are used as “pure dictionaries”, they have no issues with "__proto__" as the key.

All methods that return object properties (like Object.keys and others) – return “own” properties. If we want inherited ones, then we can use for..in.

Tasks

importance: 5

There’s an object dictionary, created as Object.create(null), to store any key/value pairs.

Add method dictionary.toString() into it, that should return a comma-delimited list of keys. Your toString should not show up in for..in over the object.

Here’s how it should work:

let dictionary = Object.create(null);

// your code to add dictionary.toString method

// add some data
dictionary.apple = "Apple";
dictionary.__proto__ = "test"; // __proto__ is a regular property key here

// only apple and __proto__ are in the loop
for(let key in dictionary) {
  alert(key); // "apple", then "__proto__"
}

// your toString in action
alert(dictionary); // "apple,__proto__"

The method can take all enumerable keys using Object.keys and output their list.

To make toString non-enumerable, let’s define it using a property descriptor. The syntax of Object.create allows us to provide an object with property descriptors as the second argument.

let dictionary = Object.create(null, {
  toString: { // define toString property
    value() { // the value is a function
      return Object.keys(this).join();
    }
  }
});

dictionary.apple = "Apple";
dictionary.__proto__ = "test";

// apple and __proto__ is in the loop
for(let key in dictionary) {
  alert(key); // "apple", then "__proto__"
}

// comma-separated list of properties by toString
alert(dictionary); // "apple,__proto__"

When we create a property using a descriptor, its flags are false by default. So in the code above, dictionary.toString is non-enumerable.

See the the chapter Property flags and descriptors for review.

importance: 5

Let’s create a new rabbit object:

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

let rabbit = new Rabbit("Rabbit");

These calls do the same thing or not?

rabbit.sayHi();
Rabbit.prototype.sayHi();
Object.getPrototypeOf(rabbit).sayHi();
rabbit.__proto__.sayHi();

The first call has this == rabbit, the other ones have this equal to Rabbit.prototype, because it’s actually the object before the dot.

So only the first call shows Rabbit, other ones show undefined:

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

let rabbit = new Rabbit("Rabbit");

rabbit.sayHi();                        // Rabbit
Rabbit.prototype.sayHi();              // undefined
Object.getPrototypeOf(rabbit).sayHi(); // undefined
rabbit.__proto__.sayHi();              // undefined
Tutorial map

Comments

read this before commenting…
  • You're welcome to post additions, questions to the articles and answers to them.
  • To insert a few words of code, use the <code> tag, for several lines – use <pre>, for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)
  • If you can't understand something in the article – please elaborate.