Intro to Object Prototypes in JavaScript
When you create a new object in JavaScript using {}
, it comes with some built-in
properties, like a toString()
function.
const obj = {};
obj.toString(); // '[object Object]'
The Mozilla docs document this function as Object.prototype.toString()
. That's because obj
is an instance of the Object
class.
When you access the toString
property, JavaScript first looks to see if obj
has a toString
property. Since it doesn't, JavaScript goes up the inheritance chain to Object.prototype
, and checks if Object.prototype
has a toString
property.
const obj = {};
obj instanceof Object; // true
obj.toString === Object.prototype.toString; // true
obj.toString = () => {};
obj.toString === Object.prototype.toString; // false
You can think of Object.prototype
as a template object that all objects inherit methods and properties from.
Adding Properties to a Prototype
A prototype is a JavaScript object like any other. That means you can add new
properties to Object.prototype
, and then every object will have access to that property.
// Add a `getAnswer()` function to _all_ objects
Object.prototype.getAnswer = function() { return 42 };
const obj = {};
obj.getAnswer(); // 42
Just because you can add methods to Object.prototype
doesn't mean you should.
Doing so may cause compatibility issues with future versions of JavaScript. For
example, the famous SmooshGate debacle was caused because
a popular library added a Array.prototype.flatten()
that conflicted with a
new built-in JavaScript function.
Creating Your Own Prototype
Suppose you have a pre-ES6 JavaScript class, which is just a plain old function
that you will call with new
.
function MyClass() {}
The MyClass
function has a prototype
property that you can modify.
function MyClass() {}
// Add a `getAnswer()` function to all instances of `MyClass`
MyClass.prototype.getAnswer = function() { return 42; };
const obj = new MyClass();
obj.getAnswer(); // 42
You can also overwrite the MyClass
function's prototype entirely.
function MyClass() {}
// Overwrite the entire prototype
MyClass.prototype = {
getAnswer: function() { return 42; }
};
const obj = new MyClass();
obj.getAnswer(); // 42
Inheriting from Another Class
The prototype object doesn't need to be a plain object. It can be an instance
of another class. To create a class MyChildClass
that inherits from MyClass
,
you set the MyChildClass
prototype to be an instance of MyClass
.
function MyClass() {}
// Overwrite the entire prototype
MyClass.prototype = {
getAnswer: function() { return 42; }
};
function MyChildClass() {}
MyChildClass.prototype = new MyClass();
const obj = new MyChildClass();
obj.getAnswer(); // 42
// `obj` is an instance of `MyChildClass`, and `MyChildClass` inherits
// from `MyClass`, which in turn inherits from `Object`.
obj instanceof MyChildClass; // true
obj instanceof MyClass; // true
obj instanceof Object; // true
MyChildClass
inherits from MyChild
, which in turn inherits from Object
.
That's because MyChildClass.prototype
is an instance of MyClass
, and then
MyClass.prototype
is an instance of object. This is what JavaScript developers
call the prototype chain.
Get An Object's Prototype
Given an object, you can get access to its prototype using .constructor.prototype
.
function MyClass() {}
const obj = new MyClass();
obj.constructor.prototype.getAnswer = function() { return 42; };
const obj2 = new MyClass();
obj2.getAnswer(); // 42
That's because there's an Object.prototype.constructor
property that points to the object's constructor. There's also a non-standard __proto__
property that behaves similarly to constructor.prototype
.
The constructor
and __proto__
properties are potential attack vectors for
prototype poisoning. Several popular JavaScript libraries, including lodash and Mongoose, have reported prototype poisoning vulnerabilities in the past.