prototype Versus __proto__
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeSuddenly, after adding calculateTotalWeight
to some strange prototype
key, we can call this method on any new instance of the Helper
object. But go back to your browser and check out the first log. Huh, our helper instance still only has one key: $wrapper
. I don't see calculateTotalWeight
here... so how the heck is that working? I mean, I don't see the method we're calling!
Hello proto
Check out that __proto__
property. Every object has a magic property called __proto__
. And if you open it, it holds the calculateTotalWeight
function. Here's the deal: when you call a method or access a property on an object, JavaScript first looks for it on the object itself. But if it doesn't find it there, it looks at the __proto__
property to see if it exists on that object. If it does, JavaScript uses it. If it does not exist, it actually keeps going to the next __proto__
property inside of the original __proto__
and tries to look for it there. It repeats that until it gets to the top level. What you are seeing here is the top-level __proto__
that every object shares. In other words, these methods and properties exist on every object in JavaScript.
Boy, if you think about it, this is a lot like class inheritance, where each __proto__
acts like a class we extend. And this last __proto__
is like some base class that everything extends.
proto and prototype?
Ok, so how does this relate to the prototype
key in our code?
// ... lines 1 - 2 | |
(function(window, $) { | |
// ... lines 4 - 63 | |
Helper.prototype.calculateTotalWeight = function() { | |
// ... lines 65 - 70 | |
}; | |
})(window, jQuery); |
Whenever you use the new
keyword, anything on the prototype
key of that object becomes the __proto__
of the newly instantiated object.
Ok, let's play with this!
Create a new variable called playObject
set to an object with a lift
key set to stuff
:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = { | |
initialize: function ($wrapper) { | |
// ... lines 6 - 11 | |
var playObject = { | |
lift: 'stuff' | |
}; | |
// ... lines 15 - 25 | |
}, | |
// ... lines 27 - 61 | |
}; | |
// ... lines 63 - 78 | |
})(window, jQuery); |
Next, say playObject.__proto__.cat = 'meow'
:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = { | |
initialize: function ($wrapper) { | |
// ... lines 6 - 11 | |
var playObject = { | |
lift: 'stuff' | |
}; | |
playObject.__proto__.cat = 'meow'; | |
// ... lines 16 - 25 | |
}, | |
// ... lines 27 - 61 | |
}; | |
// ... lines 63 - 78 | |
})(window, jQuery); |
You shouldn't normally access or set the __proto__
property directly, but for playing around now, it's great. Finally, console.log(playObject.lift)
, which we know will work, but also playObject.cat
:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = { | |
initialize: function ($wrapper) { | |
// ... lines 6 - 11 | |
var playObject = { | |
lift: 'stuff' | |
}; | |
playObject.__proto__.cat = 'meow'; | |
console.log(playObject.lift, playObject.cat); | |
// ... lines 17 - 25 | |
}, | |
// ... lines 27 - 61 | |
}; | |
// ... lines 63 - 78 | |
})(window, jQuery); |
Ok, try it. Refresh! Hey, stuff
and meow
! That's the __proto__
property in action!
Decomposing the String, Array and DateTime Object
And hey! Remember how I said that everything is an object in JavaScript, including strings and arrays? Yep, that means that they also have an __proto__
. This time, console.log('foo'.__proto__)
to see what methods and properties belong to a string object. I wonder what things I can call on an array? Let's find out: [].__proto__
. And what about a new Date()
object? Print its __proto__
too:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = { | |
initialize: function ($wrapper) { | |
// ... lines 6 - 7 | |
console.log( | |
'foo'.__proto__, | |
[].__proto__, | |
(new Date()).__proto__ | |
); | |
// ... lines 13 - 21 | |
}, | |
// ... lines 23 - 57 | |
}; | |
// ... lines 59 - 74 | |
})(window, jQuery); |
Let's see what happens! Refresh! Nice! Each is a big list of things that we can call on each type of object. Apparently strings have an indexOf()
method, a match()
method, normalize()
, search()
, slice()
and a lot more. The Array has its own big list. If you have a DateTime
instance, you'll be able to call getHours()
, getMilliseconds()
and getMinutes()
, to name a few.
To compare, let's Google for "JavaScript string methods". Check out the W3Schools result. This basically gives you the exact same information we just found ourselves: these are the methods you can call on a string. The cool part is that we now understand how this works: these are all stored on the __proto__
of each string object.
Creating Multiple Instances
The whole point of this new constructor and prototype
setup is so that we could have multiple instances of our Helper
object. The prototype
is just the key to take advantage of it.
To prove it all works, add var helper2 = new Helper()
and pass it a different $wrapper
, like the footer on our page:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = { | |
initialize: function ($wrapper) { | |
// ... line 6 | |
this.helper = new Helper(this.$wrapper); | |
var helper2 = new Helper($('footer')); | |
// ... lines 9 - 21 | |
}, | |
// ... lines 23 - 57 | |
}; | |
// ... lines 59 - 74 | |
})(window, jQuery); |
Since the footer doesn't have any rows that have weight on it, this should return zero. Log that: this.helper.calculateTotalWeight()
and helper2.calculateTotalWeight()
:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = { | |
initialize: function ($wrapper) { | |
// ... line 6 | |
this.helper = new Helper(this.$wrapper); | |
var helper2 = new Helper($('footer')); | |
console.log( | |
this.helper.calculateTotalWeight(), | |
helper2.calculateTotalWeight() | |
); | |
// ... lines 13 - 21 | |
}, | |
// ... lines 23 - 57 | |
}; | |
// ... lines 59 - 74 | |
})(window, jQuery); |
Try that! Cool! 157.5 and of course, zero.
Here's the point of all of this: you do want to setup your objects so that they can be instantiated. And now we know how to do this. First, set your variable to a function: this will become the constructor:
// ... lines 1 - 2 | |
(function(window, $) { | |
// ... lines 4 - 59 | |
/** | |
* A "private" object | |
*/ | |
var Helper = function ($wrapper) { | |
// ... line 64 | |
}; | |
// ... lines 66 - 74 | |
})(window, jQuery); |
And second, add any methods or properties you need under the prototype
key:
// ... lines 1 - 2 | |
(function(window, $) { | |
// ... lines 4 - 65 | |
Helper.prototype.calculateTotalWeight = function() { | |
// ... lines 67 - 72 | |
}; | |
})(window, jQuery); |
You can still add keys directly to Helper
, and these are basically the equivalent of static methods: you can only call them by using the original object name, like Helper.foo
or Helper.bar
.
Let's keep going: we can organize all of this a bit better. And once we have, we'll be able to make RepLogApp
object a proper, instantiatable object... with almost no work.
I was asked on an interview once "How does inheritance work in JavaScript?". I answered "prototypes" because I read it somewhere before :) Now I know what it really means, thanks!