Prototype
Javascript is a 'prototype-based' language.
As I referred to the previous post, it is very important to understand the concept of a prototype.
1. Review
- All things in JS(except primitive type) are objects
- A function is one of the objects
- All Objects are created by constructor(instance)
- When you define the function, JS delegates a constructor to function and create a prototype
Let's start our conversation with a constructor.
Constructor in JS equals to others in an object-oriented language such as Java.
It just creates an object and returns it(It is called Instance) to us.
JS engine automatically delegates constructor to function, so it is the only reason
why the function is able to use a keyword 'new' .
In other words, a variable cannot use the keyword 'new'.
function myFunction() {
//doSomething
}
var firstInstance = new myFunction();
var secondInstance = new myFunction();
firstInstance === secondInstance; // Because they are different instance
firstInstance.constructor === secondInstance.constructor; // true
var thirdInstance = new firstInstance(); // Error. Because firstInstance is a variable
2. Prototype
There is one more thing JS engine does when you define a function.
They create a prototype and prototype link.
Then what is a prototype?
It is an original form of the object.
When we call the constructor, JS clone prototype to create a new object and returns it.
Its concept looks similar to a class, but there is a vast difference.
Most of the JS developers do not understand the difference between class and prototype(including me).
Class is a blueprint of an object.
Classes inherit from classes and create subclass relationships(hierarchical model).
Once you create classes and their inheritance, they are immutable in your client code.
On the other hand, JS inheritance uses a prototype chain to wire sub to super.
So here is the biggest difference:
- The prototype is a working object instance, but class is not an instance
- Every object can have another object as its prototype(prototype link)
- It means that objects inherit from other objects 'directly'
- Object(including the prototype) is mutable since it is not a predefined blue-print like class
- Consequently, inheritance can be changed in your client code
This description can be ambiguous, but very important to understand it.
Let's go back to our Person example.
function Person(name) {
this.name = name;
this.walk = function() {
console.log(this.name + ' is waliking');
}
}
As I referred above, JS creates a prototype automatically.
Let's figure out where the prototype is.
Person.prototype;
// {constructor: ƒ}
// constructor: ƒ Person(name)
// __proto__: Object
Person.prototype.constructor;
// ƒ Person(name) {
// this.name = name;
// }
typeof Person.prototype; // object
In function Person(), there is a property 'prototype'
and it has a reference to the prototype of this function.
Similarly, in prototype, there is a property 'constructor'
and it has a reference to the constructor of this prototype.
It is like a linked-list.
Next, Let's create a new Person object using the constructor.
var me = new Person('Nam');
me;
// name: "Nam"
// walk: ƒ ()
// __proto__: Object
me.walk(); //Nam is waliking
me.__proto__; // Actually it is a anti-pattern. Do not use it in your code
// {constructor: ƒ}
// constructor: ƒ Person(name)
// __proto__: Object
var you = new Person('your name');
// name: "your name"
// walk: ƒ ()
// __proto__: Object
you; // your name is waliking
me.__proto__ === you.__proto__; // true
I created two Person objects, 'you' and 'me'.
They are different objects but have the same property and method(name and walk()).
It is all because they are created by the same prototype.
As I referred above, every object can have another object as its prototype.
You can access a prototype of constructor which builds your object using property '__proto__'.
DO NOT CONFUSE '.prototype' and '__proto__'.
Property '__proto__' reveals a prototype linkage, but do not use it in your client code.
Instead, use Object.getPrototypeOf().
The above image shows us the object 'you' and 'me' have another object(Person.prototype) as their prototype.
Now, let's improve our Person example.
People have their own name, so 'name' property can be mutable.
But consider 'walk()' method. It is not mutable.
But in the above example, all objects we created using Person() constructor have their own 'walk()' method
even though their 'walk()' methods are functionally equivalent.
So 'walk()' method is part of the original form and it means that we have to define 'walk()' method in the prototype.
Let's modify our Person example.
function Person(name) {
this.name = name;
}
// define walk() method in prototype
Person.prototype.walk = function() {
console.log(this.name + ' walks');
}
var me = new Person('Nam');
// there is no walk() method in me object
me;
// name: "Nam"
// __proto__: Object
// but you can use walk() method in Person object
me.walk(); // Nam walks
No waste of memory, and closer to the concept of object-oriented.
3. Prototype chain
Now, we are on the final topic of this post.
The object 'me' does not have 'walk()' method, but it works.
We will discuss how it is possible.
As we see, every object has '__proto__' property which exposes internal prototype linkage.
Actually when we call property or method of an object,
JS tries to find it from the source object to the 'Object.prototype' object using '__proto__'.
Here is what happened internally when we call me.walk() method :
1) Search 'walk()' method in 'me' object
2) Since it does not exist, it moves to 'me.__proto__'(It equals to Person.prototype)
3) Find 'walk()' method in 'Person.prototype' object and returns it
This behavior is called 'prototype chain'.
At the same time, it is how JS inheritance works.
4. Appendix
Let's see this example :
Person.constructor; //ƒ Function() { [native code] }
'Person.constructor' is not undefined.
So does it mean that the function 'Person' has a property '.constructor'?
Actually, there is no property '.constructor' in function API.
It just returns the constructor of another object because of the prototype chain.
Here is what happened :
1) Find 'Person.constructor' -> undefined
2) Moves to 'Person.__proto__' -> 'Function.prototype'
3) Find 'Person.__proto__.constructor' -> the constructor of 'Function.prototype'
4) Return