The prototype
property is an object created by JavaScript for every Function()
instance. Specifically, it links object instances created with the new
keyword back to the constructor function that created them. This is done so that instances can share, or inherit, common methods and properties. Importantly, the sharing occurs during property lookup. Remember from the first article, that every time you look up or access a property on an object, the property will be searched for on the object as well as the prototype chain.
A prototype object is created for every function, regardless of whether you intend to use that function as a constructor.
In the following code, I construct an array from the Array()
constructor, and then I invoke the join()
method.
Sample: sample118.html
<!DOCTYPE html><html lang="en"><body><script> var myArray = new Array('foo', 'bar'); console.log(myArray.join()); // Logs 'foo,bar'. </script></body></html>
The join()
method is not defined as a property of the myArray
object instance, but somehow we have access to join()
as if it were. This method is defined somewhere, but where? Well, it is defined as a property of the Array()
constructor's prototype property. Since join()
is not found within the array object instance, JavaScript looks up the prototype chain for a method called join()
.
Okay, so why are things done this way? Really, it is about efficiency and reuse. Why should every array instance created from the array constructor function have a uniquely defined join()
method when join()
always functions the same way? It makes more sense for all arrays to leverage the same join()
function without having to create a new instance of the function for each array instance.
This efficiency we speak of is all possible because of the prototype
property, prototype linkage, and the prototype lookup chain. In this article, we break down these often confusing attributes of prototypal inheritance. But truth be told, you would be better off by simply memorizing the mechanics of how the chain hierarchy actually works. Refer back to the first article if you need a refresher on how property values are resolved.
Why Care About the prototype
Property?
You should care about the prototype
property for four reasons.
Reason 1
The first reason is that the prototype property is used by the native constructor functions (Object()
, Array()
, Function()
, etc.) to allow constructor instances to inherit properties and methods. It is the mechanism that JavaScript itself uses to allow object instances to inherit properties and methods from the constructor function's prototype
property. If you want to understand JavaScript better, you need to understand how JavaScript itself leverages the prototype
object.
Reason 2
When creating user-defined constructor functions, you can orchestrate inheritance the same way JavaScript native objects do. But first you have to learn how it works.
Reason 3
You might really dislike prototypal inheritance or prefer another pattern for object inheritance, but the reality is that someday you might have to edit or manage someone else's code who thought prototypal inheritance was the bee's knees. When this happens, you should be aware of how prototypal inheritance works, as well as how it can be replicated by developers who make use of custom constructor functions.
Reason 4
By using prototypal inheritance, you can create efficient object instances that all leverage the same methods. As already mentioned, not all array objects, which are instances of the Array()
constructor, need their own join()
methods. All instances can leverage the same join()
method because the method is stored in the prototype chain.
Prototype Is Standard On All Function()
Instances
All functions are created from a Function()
constructor, even if you do not directly invoke the Function()
constructor (var add = new Function('x', 'y', 'return x + z');
) and instead use the literal notation (var add = function(x,y){return x + z};
).
When a function instance is created, it is always given a prototype
property, which is an empty object. In the following sample, we define a function called myFunction and then access the prototype
property which is simply an empty object.
Sample: sample119.html
<!DOCTYPE html><html lang="en"><body><script> var myFunction = function () { }; console.log(myFunction.prototype); // Logs object{} console.log(typeof myFunction.prototype); // Logs 'object'. </script></body></html>
Make sure you completely understand that the prototype property is coming from the Function()
constructor. It is only once we intend to use our function as a user-defined constructor function that the prototype property is leveraged, but this does not change the fact that the Function()
constructor gives each instance a prototype property.
The Default prototype
Property Is an Object()
Object
All this prototype
talk can get a bit heavy. Truly, prototype
is just an empty object property called "prototype" created behind the scenes by JavaScript and made available by invoking the Function()
constructor. If you were to do it manually, it would look something like this:
Sample: sample120.html
<!DOCTYPE html><html lang="en"><body><script> var myFunction = function () { }; myFunction.prototype = {}; // Add the prototype property and set it to an empty object. console.log(myFunction.prototype); // Logs an empty object. </script></body></html>
In fact, this sample code actually works just fine, essentially just duplicating what JavaScript already does.
The value of a prototype property can be set to any of the complex values (objects) available in JavaScript. JavaScript will ignore any prototype property set to a primitive value.
Instances Created From a Constructor Function Are Linked to the Constructor’s prototype
Property
While its only an object, prototype
is special because the prototype chain links every instance to its constructor function's prototype property. This means that any time an object is created from a constructor function using the new
keyword (or when an object wrapper is created for a primitive value), it adds a hidden link between the object instance created and the prototype property of the constructor function used to create it. This link is known inside the instance as __proto__
(though it is only exposed/supported via code in Firefox 2+, Safari, Chrome, and Android). JavaScript wires this together in the background when a constructor function is invoked, and its this link that allows the prototype chain to be, well, a chain. In the following sample, we add a property to the native Array()
constructors prototype
, which we can then access from an Array()
instance using the __proto__
property set on that instance.
Sample: sample121.html
<!DOCTYPE html><html lang="en"><body><script> // This code only works in browsers that support __proto__ access. Array.prototype.foo = 'foo'; var myArray = new Array(); console.log(myArray.__proto__.foo); // Logs foo, because myArray.__proto__ = Array.prototype </script></body></html>
Since accessing __proto__
is not part of the official ECMA standard, there is a more universal way to trace the link from an object to the prototype object it inherits, and that is by using the constructor
property. This is demonstrated in the following sample.
Sample: sample122.html
<!DOCTYPE html><html lang="en"><body><script> Array.prototype.foo = 'foo'; // All instances of Array() now inherit a foo property. var myArray = new Array(); // Trace foo in a verbose way leveraging *.constructor.prototype console.log(myArray.constructor.prototype.foo); // Logs foo. // Or, of course, leverage the chain. console.log(myArray.foo) // Logs foo. // Uses prototype chain to find property at Array.prototype.foo </script></body></html>
In this example, the foo
property is found within the prototype object. You need to realize this is only possible because of the association between the instance of Array()
and the Array()
constructor prototype object (Array.prototype
). Simply put, myArray.__proto__
(or myArray.constructor.prototype
) references Array.prototype
.
Last Stop In the prototype
Chain Is Object.prototype
Since the prototype property is an object, the last stop in the prototype chain or lookup is at Object.prototype
. In the code that follows, I create myArray
, which is an empty array. I then attempt to access a property of myArray
that has not yet been defined, engaging the prototype lookup chain. The myArray
object is examined for the foo property. Being absent, the property is looked for at Array.prototype
, but it is not there either. So the final place JavaScript looks is Object.prototype
. Because it is not defined in any of those three objects, the property is undefined
.
Sample: sample123.html
<!DOCTYPE html><html lang="en"><body><script> var myArray = []; console.log(myArray.foo) // Logs undefined. /* foo was not found at myArray.foo or Array.prototype.foo or Object.prototype.foo, so it is undefined. */ </script></body></html>
Take note that the chain stopped with Object.prototype
. The last place we looked for foo was Object.prototype
.
Careful! Anything added to Object.prototype will show up in a for in loop.
The prototype
Chain Returns the First Property Match It Finds In the Chain
Like the scope chain, the prototype
chain will use the first value it finds during the chain lookup.
Modifying the previous code example, if we added the same value to the Object.prototype
and Array.prototype
objects, and then attempted to access a value on an array instance, the value returned would be from the Array.prototype
object.
Sample: sample124.html
<!DOCTYPE html><html lang="en"><body><script> Object.prototype.foo = 'object-foo'; Array.prototype.foo = 'array-foo'; var myArray = []; console.log(myArray.foo); // Logs 'array-foo', which was found at Array.prototype.foo myArray.foo = 'bar'; console.log(myArray.foo) // Logs 'bar', was found at Array.foo </script></body></html>
In this sample, the foo value at Array.prototype.foo
is shadowing, or masking, the foo
value found at Object.prototype.foo
. Just remember that the lookup ends when the property is found in the chain, even if the same property name is also used farther up the chain.
Replacing the prototype
Property With a New Object Removes the Default Constructor Property
Its possible to replace the default value of a prototype
property with a new value. However, doing so will eliminate the default constructor property found in the "pre-made" prototype
object unless you manually specify one.
In the code that follows, we create a Foo
constructor function, replace the prototype
property with a new empty object, and verify that the constructor property is broken (it now references the less useful Object
prototype).
Sample: sample125.html
<!DOCTYPE html><html lang="en"><body><script> var Foo = function Foo() { }; Foo.prototype = {}; // Replace prototype property with an empty object. var FooInstance = new Foo(); console.log(FooInstance.constructor === Foo); // Logs false, we broke the reference. console.log(FooInstance.constructor); // Logs Object(), not Foo() // Compare to code in which we do not replace the prototype value. var Bar = function Bar() { }; var BarInstance = new Bar(); console.log(BarInstance.constructor === Bar); // Logs true. console.log(BarInstance.constructor); // Logs Bar() </script></body></html>
If you intend to replace the default prototype
property (common with some JS OOP patterns) set up by JavaScript, you should wire back together a constructor property that references the constructor function. In the following sample, we alter our previous code so that the constructor
property will again provide a reference to the proper constructor function.
Sample: sample126.html
<!DOCTYPE html><html lang="en"><body><script> var Foo = function Foo() { }; Foo.prototype = { constructor: Foo }; var FooInstance = new Foo(); console.log(FooInstance.constructor === Foo); // Logs true. console.log(FooInstance.constructor); // Logs Foo() </script></body></html>
Instances that Inherit Properties From prototype
Will Always Get the Latest Values
The prototype property is dynamic in the sense that instances will always get the latest value from the prototype regardless of when it was instantiated, changed, or appended. In the code that follows, we create a Foo
constructor, add the property x
to the prototype
, and then create an instance of Foo()
named FooInstance
. Next, we log the value of x
. Then we update the prototypes value of x and log it again to find that our instance has access to the latest value found in the prototype
object.
Sample: sample127.html
<!DOCTYPE html><html lang="en"><body><script> var Foo = function Foo() { }; Foo.prototype.x = 1; var FooInstance = new Foo(); console.log(FooInstance.x); // Logs 1. Foo.prototype.x = 2; console.log(FooInstance.x); // Logs 2, the FooInstance was updated. </script></body></html>
Given how the lookup chain works, this behavior should not be that surprising. If you are wondering, this works the same regardless of whether you use the default prototype
object or override it with your own. In the next sample, I replace the default prototype
object to demonstrate this fact.
Sample: sample128.html
<!DOCTYPE html><html lang="en"><body><script> var Foo = function Foo() { }; Foo.prototype = { x: 1 }; // The logs that follow still work the same. var FooInstance = new Foo(); console.log(FooInstance.x); // Logs 1. Foo.prototype.x = 2; console.log(FooInstance.x); // Logs 2, the FooInstance was updated. </script></body></html>
Replacing the prototype
Property With a New Object Does Not Update Former Instances
You might think that you can replace the prototype
property entirely at any time and that all instances will be updated, but this is not correct. When you create an instance, that instance will be tied to the prototype
that was minted at the time of instantiation. Providing a new object as the prototype property does not update the connection between instances already created and the new prototype
.
But remember, as I stated previously, you can update or add to the originally created prototype
object and those values remain connected to the first instance(s).
Sample: sample129.html
<!DOCTYPE html><html lang="en"><body><script> var Foo = function Foo() { }; Foo.prototype.x = 1; var FooInstance = new Foo(); console.log(FooInstance.x); // Logs 1, as you think it would. // Now let’s replace/override the prototype object with a new Object() object. Foo.prototype = { x: 2 }; console.log(FooInstance.x); // Logs 1. WHAT? Shouldn't it log 2 because we just updated prototype? /* FooInstance still references the same state of the prototype object that was there when it was instantiated. */ // Create a new instance of Foo() var NewFooInstance = new Foo(); // The new instance is now tied to the new prototype object value ({x:2};). console.log(NewFooInstance.x); // Logs 2. </script></body></html>
The key idea to take away here is that an objects prototype should not be replaced with a new object once you start creating instances. Doing so will result in instances that have a link to different prototypes.
User-Defined Constructors Can Leverage the Same prototype
Inheritance As Native Constructors
Hopefully at this point in the article, it is sinking in how JavaScript itself leverages the prototype
property for inheritance (Array.prototype
). This same pattern can be leveraged when creating non-native, user-defined constructor functions. In the following sample, we take the classic Person
object and mimic the pattern that JavaScript uses for inheritance.
Sample: sample130.html
<!DOCTYPE html><html lang="en"><body><script> var Person = function () { }; // All Person instances inherit the legs, arms, and countLimbs properties. Person.prototype.legs = 2; Person.prototype.arms = 2; Person.prototype.countLimbs = function () { return this.legs + this.arms; }; var chuck = new Person(); console.log(chuck.countLimbs()); // Logs 4. </script></body></html>
In this code, a Person()
constructor function is created. We then add properties to the prototype
property of Person()
, which can be inherited by all instances. Clearly, you can leverage the prototype chain in your code the same way that JavaScript leverages it for native object inheritance.
As a good example of how you might leverage this, you can create a constructor function whose instances inherit the legs
and arms
properties if they are not provided as parameters. In the following sample, if the Person()
constructor is sent parameters, the parameters are used as instance properties, but if one or more parameters are not provided, there is a fallback. These instance properties then shadow or mask the inherited properties, giving you the best of both worlds.
Sample: sample131.html
<!DOCTYPE html><html lang="en"><body><script> var Person = function (legs, arms) { // Shadow prototype value. if (legs !== undefined) { this.legs = legs; } if (arms !== undefined) { this.arms = arms; } }; Person.prototype.legs = 2; Person.prototype.arms = 2; Person.prototype.countLimbs = function () { return this.legs + this.arms; }; var chuck = new Person(0, 0); console.log(chuck.countLimbs()); // Logs 0. </script></body></html>
Creating Inheritance Chains (the Original Intention)
Prototypal inheritance was conceived to allow inheritance chains that mimic the inheritance patterns found in traditional object oriented programming languages. In order for one object to inherit from another object in JavaScript, all you have to do is instantiate an instance of the object you want to inherit from and assign it to the prototype
property of the object that is doing the inheriting.
In the code sample that follows, Chef
objects (cody
) inherit from Person()
. This means that if a property is not found in a Chef
object, it will then be looked for on the prototype of the function that created Person()
objects. To wire up the inheritance, all you have to do is instantiate an instance of Person()
as the value for Chef.prototype
(Chef.prototype = new Person();
).
Sample: sample132.html
<!DOCTYPE html><html lang="en"><body><script> var Person = function () { this.bar = 'bar' }; Person.prototype.foo = 'foo'; var Chef = function () { this.goo = 'goo' }; Chef.prototype = new Person(); var cody = new Chef(); console.log(cody.foo); // Logs 'foo'. console.log(cody.goo); // Logs 'goo'. console.log(cody.bar); // Logs 'bar'. </script></body></html>
Conclusion
All we did in this sample was leverage a system that was already in place with the native objects. Consider that Person()
is not unlike the default Object()
value for prototype properties. In other words, this is exactly what happens when a prototype property, containing its default empty Object()
value, looks to the prototype of the constructor function created (Object.prototype
) for inherited properties.
Comments