The first thing to be aware of is that there are two things in JavaScript called “prototype.” The first is a run-of-the-mill property that every function has.
function f() {}
console.log(f.prototype);
(More on that later.) The second is an under-the-hood reference between objects. In the spec, this under-the-hood reference is typically written as [[Prototype]], and I’ll continue that convention to distinguish between the two meanings of “prototype.”
The internal [[Prototype]] property is how JavaScript implements inheritance. Let’s say there’s an object A, and A’s [[Prototype]] property points to an object B. When we access A.someProperty, then JavaScript first checks if that property exists in A. If it does, then it returns its value. Otherwise, JavaScript follows the [[Prototype]] property to B. JavaScript then checks if that property exists in B. If it does, then it returns its value. Otherwise, JavaScript once again follows the [[Prototype]] to the next object. It will continue doing this until either it finds the property or until it reaches an object whose [[Prototype]] is null. (The [[Prototype]] linkage from one object to another to another is called a “prototype chain.”)
There have been some non-standard ways to manipulate the [[Prototype]] property, such as Mozilla’s proto property, but today the standard is to use Object.create.
// A plain object's [[Prototype]] is implicitly set to Object.prototype
var base = {};
/* Psuedo code: base.[[Prototype]] === Object.prototype */
base.plugin = 'Lorem ipsum';
// Object.create will give us a new object that has its [[Prototype]] set to the object we passed in
var widget = Object.create(base);
/* Pseudo code: widget.[[Prototype]] === base */
widget.render = 'Exire est';
var myWidget = Object.create(widget);
/* Pseudo code: myWidget.[[Prototype]] === widget */
myWidget.app = 'Cupis hominem';
// When we access myWidget.app,
// JavaScript checks the object "myWidget" for the property "app",
// finds it, and returns its value
console.log(myWidget.app);
// When we access myWidget.render,
// JavaScript checks the object "myWidget" for the property "render",
// but doesn't find it, so follows myWidget's [[Prorotype]] to the object "widget",
// JavaScript then checks the object "widget" for the property "render",
// finds it, and returns its value
console.log(myWidget.render);
// When we access myWidget.plugin,
// JavaScript checks the object "myWidget" for the property "plugin",
// but doesn't find it, so follows myWidget's [[Prorotype]] to the object "widget",
// JavaScript then checks the object "widget" for the property "plugin",
// but doesn't find it, so follows widget's [[Prorotype]] to the object "base",
// JavaScript then checks the object "base" for the property "plugin",
// finds it, and returns its value
console.log(myWidget.plugin);
The mileage we get out of this [[Prototype]] behavior is all we really need to make useful and complex inheritance hierarchies. However, the powers that be wanted to make JavaScript more palettable to Java programmers. This is where constructor functions, their prototype property, and the “new” keyword come into play. These three things are used to make JavaScript look more like Java, even though it ultimately uses [[Prototype]] to make things work.
We start with a constructor function. Technically, any function could be a constructor. It only depends on whether we choose to treat a function as such by invoking it with “new”.
function Widget() {
this.constructed = true;
}
var instance = new Widget();
When we invoke a function with “new”, the first thing that happens is a new object is created (what we think of as the instance object). That instance object has its [[Prototype]] set… but set to what? This is where the function’s prototype property becomes important. The instance object’s [[Prototype]] property will be set to the constructor function’s prototype property. Finally, the constructor function is executed, using the instance object as the value for “this”.
If we were to simiulate the behavior of “new”, here’s how that would look.
function simulateNew(constructorFunction) {
// Create a new object who's [[Prototype]] is the constructor function's prototype
var instance = Object.create(constructorFunction.prototype);
// Execute the constructor function using our new instance as "this"
constructorFunction.call(instance);
// Finally, return the new instance
return instance;
}
// Now these two lines will do the same thing
var instance = new Widget();
var instance = simulateNew(Widget);
Since every instance’s [[Prototype]] will point to Widget.prototype, that means whatever functions or properties we assign to Widget.prototype will be available to every instance via the prototype chain.