Methods for prototypes

There are also other ways to get/set a prototype, besides those that we already know:

For instance:

let animal = {
  eats: true
};

let rabbit = Object.create(animal);

alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // true

Object.setPrototypeOf(rabbit, {}); // reset the prototype of rabbit to {}
Object.create to make a clone

Object.create can be used to perform a full object cloning:

// fully identical shallow clone of obj
let clone = Object.create(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]]. But not an in-depth copy of course.

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 that was all. So browsers implemented a more powerful, but 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, and also __proto__ became a part of the Annex B of the standard (optional for non-browser environments), because almost all browsers implemented it.

And now we have all these ways at our disposal.

But please note: for most practical tasks, prototype chains are fixed: 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. But it is possible.

“Very plain” objects

As we know, objects can be used as associative arrays to store key/value pairs.

…But if we try to use it for any keys (user-provided for instance), we can see an interesting glitch.

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’s because __proto__ must be either an object or null, a string can not become a prototype.

We did not intend to implement such behavior, right? So that’s a bug. Here the consequences are not terrible. But in more complex cases the prototype may indeed be changed, so the execution may 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.

Such thing happens only with __proto__. All other properties are “assignable” normally.

So, what’s going and and 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 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 assotiative 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. If needed, we can add a toString of our own.

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:

These methods only list enumerable properties, and those that have strings as keys.

If we want symbolic properties:

If we want non-enumerable properties:

If we want everything:

All of them operate on the object itself. Properties from the prototype are not listed.

But for..in loop is different: it also gives inherited properties.

For instance:

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:

So when rabbit.hasOwnProperty is called, the method hasOwnProperty is taken from Object.prototype. Why hasOwnProperty itself does not appear in for..in loop? The answer is simple: it’s not enumerable just like all other properties of Object.prototype.

As a take-away, let’s remember that if we want inherited properties – we should use for..in, and otherwise we can use other iteration methods or add the hasOwnProperty check.

Summary

Here’s a brief list of methods we discussed in this chapter – as a recap:

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

Object.create(null) doesn’t have any object properties and methods, and __proto__ also doesn’t exist for it.

Tasks

importance: 5

There’s an object dictionary, suited 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 toString

// 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 to provide an object with property descriptors as the second argument.

let dictionary = Object.create(null, {
  toString: { // define toString property
    value() { // with function value
      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__"
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.