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
With great prototype power comes great responsibility
Eugene Karataev ・ Jul 13 '19 ・ 1 min read
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.
Top comments (2)
A small aside: I wonder why it was decided to use two underscores for the name proto ?
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
ofObject
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! 😉