DEV Community

Eugene Karataev
Eugene Karataev

Posted on

JavaScript prototypes by example

Prototypes in JavaScript are the mechanism to share common functionality between objects. They are powerful, but sometimes confusing. Let's explore prototype behavior by example. You can try and experiment with examples below in a browser's developer tools.

We start with constructor function Object to create object instances

typeof Object; // function
Enter fullscreen mode Exit fullscreen mode

It has prototype property with useful methods like toString, valueOf, e.t.c.

Object.prototype; // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Enter fullscreen mode Exit fullscreen mode

Two ways of object creation

var obj1 = new Object();
var obj2 = {};
Enter fullscreen mode Exit fullscreen mode

Every object instance on creation receives __proto__ property

var obj = {};
obj.__proto__; // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Enter fullscreen mode Exit fullscreen mode

Object.getPrototypeOf is a modern replacement to the old __proto__ property

var obj = {};
Object.getPrototypeOf(obj) === obj.__proto__; // true
Enter fullscreen mode Exit fullscreen mode

An object's __proto__ and Object.prototype is exactly the same object

var obj = {};
obj.__proto__ === Object.prototype; // true
Enter fullscreen mode Exit fullscreen mode

Objects are different, but share the same prototype

var obj1 = {};
var obj2 = {};
obj1 === obj2; // false
obj1.__proto__ === obj2.__proto__; // true
Enter fullscreen mode Exit fullscreen mode

Prototype chain terminates with null

var obj = {}
obj.__proto__.__proto__ // null
Enter fullscreen mode Exit fullscreen mode

A property added to prototype is available to all instances (it's not recommended to modify built-in prototypes!)

var obj1 = {};
var obj2 = {};
obj2.foo // undefined
obj1.__proto__.foo = 'bar';
obj2.foo; // bar
Enter fullscreen mode Exit fullscreen mode

It's possible to create an object without a prototype. In this case handy methods like toString, valueOf, e.t.c. would not be available

var obj1 = {a: 1};
var obj2 = Object.create(null);
obj2.__proto__; // undefined
obj1 + ''; // "[object Object]"
obj2 + ''; // Uncaught TypeError: Cannot convert object to primitive value
Enter fullscreen mode Exit fullscreen mode

It's possible to change an object's __proto__ link at any time

var obj = {};
obj.toString(); // "[object Object]"
Object.setPrototypeOf(obj, null);
obj.toString(); // Uncaught TypeError: obj.toString is not a function
Object.setPrototypeOf(obj, Object.prototype);
obj.toString(); // "[object Object]"
Enter fullscreen mode Exit fullscreen mode

You can construct prototype chains of any length

var obj1 = {};
var obj2 = Object.create(obj1);
obj2.__proto__ === obj1; // true
Enter fullscreen mode Exit fullscreen mode

If a property isn't found in an object, it's searched in prototype chain all way to the top

var obj1 = {a: 1};
var obj2 = Object.create(obj1);
obj2.hasOwnProperty('a'); // false
obj2.a // 1
Enter fullscreen mode Exit fullscreen mode

Properties creation happens in a current object, not in a prototype

var obj1 = {a: 1};
var obj2 = Object.create(obj1);
obj2.hasOwnProperty('a'); // false
obj2.a; // 1
obj2.a = 2;
obj2.hasOwnProperty('a'); // true
obj2.a; // 2
obj2.__proto__.a; // 1
Enter fullscreen mode Exit fullscreen mode

A property value is searched in a current object first. If it's not found, the search continues in the prototype chain until the property is found or prototype chain ends. This may cause performance issues as described in the post below

Primitives has their own prototypes

var n = 1;
n.__proto__ === Number.prototype; // true
n.__proto__.__proto__ === Object.prototype; // true
Enter fullscreen mode Exit fullscreen mode

You can't change prototype of Object.prototype

Object.setPrototypeOf(Object.prototype, {}); // Uncaught TypeError: Immutable prototype object '#<Object>' cannot have their prototype set
Enter fullscreen mode Exit fullscreen mode

Cyclic proto chains are prohibited

var obj1 = {};
var obj2 = {};
Object.setPrototypeOf(obj1, obj2);
Object.setPrototypeOf(obj2, obj1); // Uncaught TypeError: Cyclic __proto__ value
Enter fullscreen mode Exit fullscreen mode

It was common to create function constructors and extends their prototypes with useful methods available to all instances

var Cat = function() {};
Cat.prototype.sayHi = function() {return 'meow'};
var cat = new Cat();
cat.__proto__ === Cat.prototype; // true
cat.sayHi(); // "meow"
Enter fullscreen mode Exit fullscreen mode

ES2015 class is a syntactic sugar on prototypes

class Animal {};
var cat = new Animal();
cat.__proto__ === Animal.prototype; // true
Enter fullscreen mode Exit fullscreen mode

Extending a class means extending a prototype chain

class Animal {};
class Cat extends Animal {};
var cat = new Cat();
cat.__proto__.__proto__ === Animal.prototype; // true
Enter fullscreen mode Exit fullscreen mode

Please share your examples of working with prototypes in the comments section.

Further reading

MDN

You Don't Know JS

The Modern JS Tutorial

Top comments (2)

Collapse
 
nickfazzpdx profile image
Nicholas Fazzolari

A small aside: I wonder why it was decided to use two underscores for the name proto ?

Collapse
 
somedood profile image
Basti Ortiz • Edited

That's a great question, Nicholas! It usually boils down to the discussion of "performance" and "best practices". It's often regarded to be a "bad practice" to tamper with the prototype—especially at runtime—because of the performance implications.

Moreover, the side effects of tampering with the prototype may introduce some unwanted bugs and security vulnerabilities.

For instance, let's say that you're using a third-party library. Wouldn't it be bad news if the library author tampered with the prototype of Object in a malicious manner? This type of attack is what's known as prototype pollution.

This is one of the reasons why ES6 classes were added: to provide a relatively "safer" way of tampering with the prototype.

So to finally answer your question, the reason why we use double underscores is to deter people from tampering with the prototype in the first place. Double underscores are scary to look at. By naming it that way, we are basically warning ourselves not to mess with it because of the performance and security implications.

Hope this answers your question! 😉