-
Notifications
You must be signed in to change notification settings - Fork 1
JavaScript Prototypes
- Introduction
- Property Lookup
- Object and Prototype
- Inheritance
-
Whenever you create functions, which are treated as objects in JavaScript, the JavaScript engine creates two objects for us - first is the function object itself and other is prototype object.
-
Prototypes are the objects that are created when we work with functions. They exists as a property on the function being created. E.g.
function abc() {} abc; => f abc() {} abc.prototype; => {constructor: ƒ}
-
Initially, this prototype is an empty object, but it is useful when you create objects using functions.
function abc() {} new abc(); => new abc(); // output abc {} __proto__: constructor: ƒ abc() __proto__: Object
- when we create objects using function, newly created objects get one property called dunder proto
__proto__
. This in turn points to theprototype
property of the parent function.
- when we create objects using function, newly created objects get one property called dunder proto
-
If you create a property on parent function, corresponding child object can also access that property using prototype lookup feature.
-
Lets create a property
fname
on our function prototypeabc.prototype.fname = 'aakash';
-
If we try to print
fname
property on ourfoo
object, we get the output asaakash
even though we didn't set any property onfoo
object itselffoo.fname; => // 'aakash'; foo.__proto__.fname; => // 'aakash';
-
What happens behind the scene is - On giving command
foo.fname
JavaScript engine askfoo
object whether it has any property namedfname
,foo
says NO, so JavaScript engine refers tofoo
parent i.e.abc.prototype
viafoo.__proto__
and fetches the property fromabc()
. -
JavaScript engine always checks if property is present on object, if not, it goes one level-up to object's parent prototype and fetches property from there. If parent prototype too don't have that property, this look-up continues till Object.prototype.
-
If we now print,
abc.prototype
and thenfoo.__proto__
, we see both have same propertiesabc.prototype // {fname: "aakash", lname: "goplani", constructor: ƒ} foo.__proto__ // {fname: "aakash", lname: "goplani", constructor: ƒ} abc.prototype === foo.__proto__ //true
-
If object has it's own property that was requested for, look-up process does not takes place. E.g. If JavaScript engines request for
lname
property onfoo
object, and if that property was present infoo
object, JavaScript engines fetches this property and does not performs any look-up. Only in the case where we don't have this property on requested object, a look-up operation is made.
-
If we create multiple objects from same function, prototypes will enable us to import common behaviors of the function. Example: Lets say I have an employee function
function Employee(name) { this.name = name; }
-
If we create multiple objects from this function:
var emp1 = new Employee('emp1'); var emp2 = new Employee('emp2'); emp1.name; // emp1 emp2.name; // emp2
-
If we decide to associate a common property to be shared with all child objects created from the parent function, we can create a propery on function's prototype since that will be accessible by all the child objects:
Employee.prototype.getCompanyName = function() { return 'test'; };
-
Now if we output our employee objects, they will all have new property
getCompanyName()
emp1.name; // emp1 emp2.name; // emp2 emp1.getCompanyName(); // test emp2.getCompanyName(); // test
- When we create objects from functions there are multiple references that are created behind the scenes:
-
prototype
- every function gets a new property that is an object namedprototype
. This will be shared by all the child objects that will be created using this function -
__proto__
- dunder proto property is assigned to every object (including functions) that points toprototype
property of parent object -
constructor
- every prototype object created has a referenceconstructor
that points to the parent object.
-
-
With the help of constructor property we can easily find out which function created this object
emp1.__proto__.constructor // output ƒ Employee(name) { this.name = name; }
- You can create a new object based on the return function value
var emp3 = new emp1.__proto__.constructor('emp3'); emp3; // output Employee {name: "emp3"} name: "emp3" __proto__: companyName: "acc" name: "emp3" getCompanyName: ƒ () constructor: ƒ Employee(name) __proto__: Object
- And then its constructor value still point to parent
Employee()
function
emp3.__proto__.constructor // output ƒ Employee(name) { this.name = name; }
- You can create a new object based on the return function value
-
As these are just references, you can manipulate them very easily:
emp1.__proto__.constructor = function bar() {}; var emp4 = new emp1.__proto__.constructor('emp3'); emp4; // bar {} emp4.__proto__.constructor // ƒ bar() {}
-
We have global function named
Object()
that helps to create objects. Example:var obj = new Object(); obj; // Object {}
-
We can also create objects using literal notation. Example:
var simple_obj = {}; simple_obj; // Object {}
-
Internally, they both are same i.e.
{} === new Object()
Object.prototype // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} obj.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} simple_obj.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} simple_obj.__proto__ === Object.prototype
-
When we created new objects using constructor function, a property
__proto__
gets assigned to newly created objects and it points toprototype
property of parent function. -
In other words, objects are only created when we use
new
or literal notation, so how doesprototype
object gets created when you write a constructor function? Answer is: it is automatically created usingnew Object()
by JavaScript engine -
This newly created prototype object, like other object, has
__proto__
property which in turn points to its parent. It goes on tillObject.protoype
-
Now
Object.protoype
also has__proto__
which point tonull
- that marks end of hierarchy!
-
A use case where we have two classes Employee and Manager. Manager needs to inherit properties from Employee as well as access its own properties.
-
Let say we have a Employee class with property
name
and we havegetName()
property on Employee's prototype.function Employee(name) { this.name = name; } Employee.prototype.getName = function() { return this.name; }; var emp1 = new Employee('aakash'); emp1.getName(); // "aakash"
-
On the other hand we have a Manager class with property
name
anddept
and we havegetDept()
property on Manager's prototype.function Manager(name, dept) { this.name = name; this.dept = dept; } Manager.prototype.getDept = function() { return this.dept; }; var mgr = new Manager('mike', 'sales'); mgr.getDept(); // "sales" mgr.getName(); // ERROR
-
Here we want to access
getName()
property which is present in Employee, but as soon we access that we get error becausegetName()
property is not defined for Manager, it is defined for Employee instead. We need to inherit this property into Manager class. -
Every Object has dunder proto property pointing to its parent prototype which in the end point to Object prototype.
-
One solution is to move
getName()
property to the Object's prototype so that every object created would have this property. Disadvantage of this approach is - objects which are unrelated to these two classes can also accessgetName()
property. -
Other solution is to change the reference of Manager dunder proto from pointing-to Object's prototype to point-to Employee's prototype
mgr.__proto__.__proto__ = Employee.prototype mgr.getName(); // "mike"
Topics Covered
Introduction
-
Prototype is the property attached with functions and objects. By default functions has prototype property but object does not.
function abc() {} console.log(abc.prototype) // {constructor: ƒ} i.e. prototype object pqr = {} console.log(pqr.prototype) // undefined console.log(pqr.__proto__) // {constructor: ƒ} i.e. prototype object
-
An function's prototype is the Object instance that will become the prototype for all objects created using this function as constructor
var a = new abc(); console.log(abc.prototype === a.__proto__) // true abc.prototype.name = 'test' console.log(abc.prototype) // {name: test, constructor: ƒ} console.log(a.__proto__) // {name: test, constructor: ƒ} AUTOMATICALLY CHANGED a.__proto__.name = 'test 2' console.log(abc.prototype) // {name: 2, constructor: ƒ} AUTOMATICALLY CHANGED
-
An Object's prototype is the Object instance which the object is inherited from.
-
If a property is not found on an object, it checks its corresponding prototype to fetch the value
a.name // true -> fetches from prototype a.__proto__.name // true a.name = 1 // sets new property on a a.__proto__.name // test a.__proto__.name === a.name // false i.e. (test != 1) a.__proto__.name === a.__proto__.name // true (test === test)
Changing Prototype Reference
-
Now
a
points to the prototype ofabc
. If we change the prototype reference ofabc
to point to a new objectabc.prototype = { name: 'xyz' };
-
Now
a.__proto__
is not equal toabc.prototype
.a.__proto__
holds valuetest
whereasabc.prototype
holds valuexyz
. -
If we create new object from
abc
nowvar b = new abc(); console.log(abc.prototype === b.__proto__) // true console.log(a.__proto__ === b.__proto__) // false
-
a
andb
both point to different object instances in the memory. When we changes the prototype reference, we actually changes the reference in memory so new objects created henceforth will be pointing to this new memory reference. And hence new memory reference will not be equal to the object instance at old memory instance.
Multiple Inheritance
-
Object referes to its parent prototype which in turn points to its own parent. This goes on till top level
Object
which finally points tonull
a.__proto__ === abc.prototype a.__proto__.__proto__ === Object.prototype a.__proto__.__proto__.__proto__ === null
-
We can discuss JavaScript Inheritance with simple example. Here we have a Person function and a Student function that will inherit properties from Person function.
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
Object.defineProperty(this, 'fullName', {
get: function() {
return this.firstName + ' ' + this.lastName;
},
enumerable: true
});
}
function Student(firstName, lastName, age) {
Person.call(this, firstName, lastName, age); // line 1
this._enrolledCourses = [];
this.enroll = function(courseId) {
this._enrolledCourses.push(courseId);
}
this.getCourses = function() {
return this.fullName + "'s enrolled courses are: " + this._enrolledCourses.join(', ');
}
}
Student.prototype = Object.create(Person.prototype); // line 2
Student.prototype.constructor = Student; // line 3
const aakash = new Student('Aakash', 'Goplani', 26);
aakash.enroll('Physics');
aakash.enroll('Chemistry');
aakash.enroll('Maths');
console.log('Courses enrolled so far: ', aakash.getCourses()); // Aakash Goplani's enrolled courses are: Physics, Chemistry, Maths
- Three lines are very important here that actually makes inheritance work
-
Person.call(this, firstName, lastName, age);
=> with this we are calling Person function each time we create Student Object so that Student object can have access to Person function properties. -
Student.prototype = Object.create(Person.prototype);
=> We want to create new Person Object and assign it to Student so that Student can point to Person function. Here we cannot usenew
keyword (i.e.Student = new Person(...)
) since that will execute Person function and we don't want to do that, we just want to assign Person Object reference to Student which is done byObject.create()
-
Student.prototype.constructor = Student;
=> Every prototype has aconstructor
property which points to the function that created the object. So initiallyStudent.prototype.constructor
will point to Student function but as soon as we execute// line 2
, the constructor property will now point toPerson
function. to make sure it correctly points toStudent
we write this line.