JavaScript Native Prototypes

The "prototype" property is commonly used by the core of JavaScript. It is used by all the built-in constructor functions.

Let’s start at the details.

Object.prototype

Imagine you output an object that is empty:

let obj = {};
console.log(obj); // "[object Object]" ?

Probably, you wonder where the code creating the string "[object Object]" is. It is the built-in toString method. But, in fact, the obj is empty.

Note that short notation obj = {} is equal to obj = new Object(), where Object is a built-in object constructor function along with its prototype referencing an immense object with toString or other methods.

Whenever, a new Object() is invoked, its [[Prototype]] is set to Object.prototype:

As you can see, when you call obj.toString(), the method is taken from Object.prototype.

You have the option of checking it, acting like this:

let obj = {};
console.log(obj.__proto__ === Object.prototype); // true
// obj.toString === obj.__proto__.toString == Object.prototype.toString

Please, take into account that the prototype doesn’t exist any more in the chain above Object.prototype.

It looks like this:

console.log(Object.prototype.__proto__); // null

Other Built-in Prototypes

Array, Date, Function, and other built-in objects keep methods in prototypes, as well.

For example, if you create an array ['a', 'b', 'c'], the default new Array() constructor is implemented internally. Hence, Array.prototype grows into its prototype providing methods. It is quite efficient for memory. There is Object.prototype on the top of overall built-in prototypes. The popular opinion that “ everything inherits from objects” comes from here.

For the manual check of the prototypes, you should act like this:

let arr = ['a', 'b', 'c'];
// it inherits from Array.prototype?
console.log(arr.__proto__ === Array.prototype); // true
// then from Object.prototype?
console.log(arr.__proto__.__proto__ === Object.prototype); // true
// and null on the top.
console.log(arr.__proto__.__proto__.__proto__); // null

Part of the methods in prototype can possibly overlap. For example, the Array.prototype has its toString, which is targeted at listing comma-delimited elements, as follows:

let arr = ['a', 'b', 'c'];
console.log(arr); // a,b,c <-- the result of Array.prototype.toString

Also, the Array.prototype obtains toString, but Array.prototype is nearer in the chain. Hence, the array option is used.

Chrome developer console also shows inheritance.

Other built-in objects operate similarly. The functions, as well, are objects of a built-in Function constructor. Their methods (call , apply, and more) are taken from Function.prototype . Likewise, the functions have toString .

function f() {}
console.log(f.__proto__ == Function.prototype); // true
console.log(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects

Primitives

Now, let’s see what happens with strings, booleans, and numbers. As you already know, they are not objects. But when you try to access their properties, temporary wrapper objects are generated using built-in constructors, such as String, Number, and Boolean. After providing methods, they disappear.

They are created out of your sight, and most of the engines optimize them out. Their methods, as well, reside in prototypes, available as Number.prototype, String.prototype, and Boolean.prototype.

Special values, such as null and undefined , don’t have wrappers. Therefore, methods and properties are not available to them. Neither they have matching prototypes.

Modifying Native Prototypes

It is possible to modify native prototypes. Adding a method to String.prototype makes it available to all the strings.

For instance:

String.prototype.welcome = function () {
  console.log(this);
};
"Welcome to W3Docs".welcome(); // Welcome to W3Docs

It is essential to know that prototypes are global. Hence, there is a high possibility of getting a conflict. For example, if two libraries add a String.prototype.welcome method, one will overwrite the method of the other. So, it is not considered a good idea to modify a native prototype.

In nowadays programming, modifying native prototypes can be approved in one case. It’s polyfilling. Polyfilling is used to make a substitute for a method that exists in the JavaScript specification. But it’s not yet supported by a specific JavaScript engine.

It is necessary to implement it manually, populating the built-in prototype with it.

Here is an example:

if (!String.prototype.repeat) {
  // add it to the prototype, if there's no such method
  String.prototype.repeat = function (n) {
    // repeat the string n times
    //literally, the code can be a bit more complex than that ( find the full 
    //algorithm in the specification), but even not perfect polyfill may often be
    //considered fine
    return new Array(n + 1).join(this);
  };
}
console.log("Ha".repeat(2)); // HaHa

Borrowing from Prototypes

We have already spoken about method borrowing in the chapter Decorators and Forwarding, Call/Apply.

It’s when one takes a method from an object and copies it into another. Often some of the native prototypes’ methods are borrowed. For example, if you are creating an array-like object, you may need to copy several Array methods to it:

let obj = {
  0: "Welcome",
  1: "W3Docs!",
  length: 2,
};
obj.join = Array.prototype.join;
console.log(obj.join(' to ')); // Welcome to W3Docs!

Another way of inheriting is to set obj.__proto__ to Array.prototype . Thus, all array methods can be automatically available in obj . But that is impossible when obj inherits from another one. Note that you can inherit from one object only once.

Borrowing methods allow the mixing of functionalities from different objects.




Do you find this helpful?

Related articles