loading...

JavaScript prototypes by example

karataev profile image Eugene Karataev ・3 min read

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

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

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

Two ways of object creation

var obj1 = new Object();
var obj2 = {};

Every object instance on creation receives __proto__ property

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

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

var obj = {};
Object.getPrototypeOf(obj) === obj.__proto__; // true

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

var obj = {};
obj.__proto__ === Object.prototype; // true

Objects are different, but share the same prototype

var obj1 = {};
var obj2 = {};
obj1 === obj2; // false
obj1.__proto__ === obj2.__proto__; // true

Prototype chain terminates with null

var obj = {}
obj.__proto__.__proto__ // null

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

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

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

You can construct prototype chains of any length

var obj1 = {};
var obj2 = Object.create(obj1);
obj2.__proto__ === obj1; // true

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

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

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

You can't change prototype of Object.prototype

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

Cyclic proto chains are prohibited

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

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"

ES2015 class is a syntactic sugar on prototypes

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

Extending a class means extending a prototype chain

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

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

Further reading

MDN

You Don't Know JS

The Modern JS Tutorial

Discussion

pic
Editor guide
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 (Some Dood)

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! 😉