Let’s try to understand the concept of prototype in JavaScript. This is one of the concept which many people may have had time understanding.
Just make a mental note of the below point. We will understand it more deeply in the coming sections.
- prototype is only available on the constructor function
- __proto__ is available on all JavaScript objects, including functions.
So, let’s see whether we can tackle this topic using our Concept Diagrams.
Let’s take the below snippet of code and ask ourselves some questions like what will be printed?
What will be printed?
let factory = {};
console.log("factory: name: ", factory.name);
console.log("factory: toString(): ", factory.toString());
Ofcourse, you can take an intelligent guess. The factory.name should print “undefined” as we can see there is nothing called “name” property within the empty { }.
But the factory.toString() prints [object object].
Interesting, right! That’s because every object has access to an internal (secret and not so secret) property called “__proto__”, which exposes many additional methods common to all objects in JavaScript.
Here our first concept diagram for our mental model.
The __proto__ contains the following methods. The one with __ are special internal methods.
console.log("factory: __proto__: ", factory.__proto__);
Prototypes are at the heart of several other JavaScript features.
So, let’s get the ball rolling and understand more about prototypes.
Prototypes
Let’s take a close look at the code and the related concept diagram.
var scene = {
is3D: false
}
shape = {
__proto__: scene,
name: "shape"
}
Let’s try to access is3D property from the shape object.
console.log(shape.is3D); // false
The sequence looks as below concept diagram when trying to access shape.is3D property.
- Follow the
shape
wire. It leads to an object. - Does this object have a
is3D
property?- No.
- But it has a prototype. Let’s check it out.
- Does that object have a
is3D
property?- Yes, it points at “true”.
- Therefore, the result of
shape.is3D
istrue
.
Prototype Chaining
A prototype isn’t a special “thing” in JavaScript. A prototype is more like a relationship or connection. An object may point at another object as its prototype and get all the properties and methods defined on that object.
Let’s understand prototype chaining with an example.
// By specifying __proto__ we instruct JavaScript to continue looking for missing properties on that object instead.
var scene = {
is3D: false
}
shape = {
__proto__: scene,
name: "shape"
}
square = {
__proto__: shape,
side: 20
}
// Shadowing, is3D of cube shadows that of scene:is3D
cube = {
__proto__: shape,
is3D: true
}
Let’s write the code to verify our understanding.
console.log("scene:is3D: ", scene.is3D); // false
console.log("shape:is3D: ", shape.is3D); //false;
console.log("square:side:", square.side); // 20
console.log("cube:is3D", cube.is3D); // true;
console.log("square.is3D: ", square.is3D); // false;
Concept diagram for prototype chain
Prototype Shadowing
Shadowing happens when the object redefines one or more of the properties that is already on the prototype object.
// By specifying __proto__ we instruct JavaScript to continue looking for missing properties on that object instead.
var scene = {
is3D: false
}
shape = {
__proto__: scene,
name: "shape"
}
// Shadowing, is3D of cube shadows that of scene:is3D
cube = {
__proto__: shape,
is3D: true
}
In the example above, the is3D property of cube shadows the is3D property on the scene object.
Concept Diagram for Prototype shadowing
In the above concept diagram it is very clear that both scene and cube has it own wire to the is3D property.
In other words, once we find our property, we stop the search.
If you ever want to check if an object has its own property wire with a certain name, you can call a built-in function called hasOwnProperty. It returns true for “own” properties, and does not look at the prototypes.
In our last example, both scene and cube have their own is3D wires, so it is true for both:
console.log(square.hasOwnProperty('is3D')); // false
console.log(scene.hasOwnProperty('is3D')); // true;
console.log(shape.hasOwnProperty('is3D')); // true
What happens when you dynamically create property on an object that already has same property on its prototype?
Let’s understand this with the below code base.
let shape = {
name: "shape 1"
};
let square = {
__proto__: shape,
// Note: no name property of its own
};
In the above block of code as of declaration the square object doesn’t have it’s own name property.
So, if you try to execute the below code block
console.log(shape.name); // ? "shape 1"
console.log(square.name); // {} -> prototype->shape-> name => "shape 1"
Now let us add the name property to the square object at runtime as shown below.
square.name = "square 1"
And now let’s take a look at the logs.
console.log(shape.name); // => "shape 1"
console.log(square.name); // => square has name so => "square 1"
console.log(shape.name); // => "shape 1"
Rule of Thumb
We can summarize this behavior with a simple rule of thumb.
When we read a property that doesn’t exist on our object, then we’ll keep looking for it on the prototype chain.
If we don’t find it, we get undefined.
But when we write a property that doesn’t exist on our object, that will create that property on our object.
Creating an object with no prototype
If you wish to have bare minimum thing in your object you can create an object with no prototype as shown below.
Be careful all common methods from the prototype will not be available.
If you have a usecase for this, please feel free to share in the comment.
Consider the code block below.
let alone = {
__proto__: null
};
The above code block will produce an object that truly doesn’t have a prototype, at all. As a result, it doesn’t even have built-in object methods:
console.log(alone.hasOwnProperty); // undefined
console.log(alone.toString); // undefined
You won’t often want to create objects like this, if at all. However, the Object Prototype itself is exactly such an object. It is an object with no prototype.
Food for thought
If JavaScript searches for missing properties on its’ immediate prototype, and most objects share the same prototype, can we make new properties or even methods “appear” on all objects by mutating that prototype?
The answer is "yes".
Let’s take a look at the below code block. First we create a base shape object that contains common properties for all shapes like position, attributes etc.
let shape = {
position: {
x: 0,
y: 0
},
attributes: {
fill: "red",
color: "blue"
}
};
Let’s create a new object using the “shape” object as it’s prototype. Here we use the Object.create method. You can pass in the base prototype to use when creating object.
let square = Object.create(shape);
square.size = 10;
Here just for quick test we also dynamically create a size property onthe square object.
Now, our square object as the new size property as well as access to position and attributes from the shape object.
Let’s create a save method on a different object altogether and mutate the prototype. The actual logic is not important from the core discussion point there.
let obj = {};
obj.__proto__.save= function () {
// Write your own logic here.
// You can use "this" to access the respective derived object.
console.log(this);
console.log("Write the logic to save or serialize object here...")
}
The above code has no direct connection to “shape” object.
We mutated the Object Prototype by adding a save
method to it. As a result all object using shape as the prototype will have access to the save() method.
So, we can use the below code block to invoke the save method.
console.log(square.save());
You will get the message “Write the logic to save or serialize object here…” on the console.
Let’s create one more object, for e.g. circle, using the shape prototype.
let circle = Object.create(shape);
circle.radius = 50;
console.log(circle.save());
And you will observe the circle property also has access to the save() method.
“this” will point to the respective object, if called from circle, it will be circle object and if save is called from square, “this” will point to square.
Let’s draw our concept diagram one more time to make this more clear
Assignment for beginners or those interested (if you choose to take up)
Draw up the concept diagram for “square” and “circle” object and share as comments here.
NOTE:
This is an early draft for quick review. Updates will be coming soon with more examples.