Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Suddenly, 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.

Leave a comment!

3
Login or Register to join the conversation
Graymath technology Avatar
Graymath technology Avatar Graymath technology | posted 4 years ago

playObject.prototype.cat = 'meow'; why does this given an error ?

Reply

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!

Reply

Hey boykodev!

I'm glad to hear that you are finding useful our tutorials :D

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.1.*", // v3.1.10
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.1
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "symfony/swiftmailer-bundle": "^2.3", // v2.4.0
        "symfony/monolog-bundle": "^2.8", // 2.12.0
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.1
        "symfony/phpunit-bridge": "^3.0" // v3.1.6
    }
}
userVoice