Methods for prototypes

Domains: Javascript

In this chapter we cover additional methods to work with a prototype.

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

  • Object.create(proto[, descriptors]) -- creates an empty object with given proto as Prototype and optional property descriptors.
  • Object.getPrototypeOf(obj) -- returns the Prototype of obj.
  • Object.setPrototypeOf(obj, proto) -- sets the Prototype of obj to 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 info:property-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. The __proto__ was de-facto implemented everywhere, so it 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.

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. 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 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 did not 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 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.

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 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 = "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:

  • Reflect.ownKeys(obj) -- returns an array of all own property names.

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.

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:

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.

Summary

Here's a brief list of methods we discussed in this chapter -- as a recap:

  • Object.create(proto[, descriptors]) -- creates an empty object with given proto as Prototype (can be null) and optional property descriptors.
  • Object.getPrototypeOf(obj) -- returns the Prototype of obj (same as __proto__ getter).
  • Object.setPrototypeOf(obj, proto) -- sets the Prototype of obj to proto (same as __proto__ setter).
  • Object.keys(obj) / Object.values(obj) / Object.entries(obj) -- returns an array of enumerable own string property names/values/key-value pairs.
  • Object.getOwnPropertySymbols(obj) -- returns an array of all own symbolic property names.
  • Object.getOwnPropertyNames(obj) -- returns an array of all own string property names.
  • Reflect.ownKeys(obj) -- returns an array of all own property names.
  • obj.hasOwnProperty(key): it returns true if obj has its own (not inherited) property named key.

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.

Similar pages

Page structure
Terms

Objects

Prototypal inheritance

Data properties

Array

NULL

alert

String

Set

ToString

Loops

JavaScript

Date and time

Assignment =

Switch statement

Functions

Constructor

Map

Try..catch