JavaScript Prototypes - aakash14goplani/FullStack GitHub Wiki


Introduction

  • 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: ƒ} 
    

    prototype_function

  • 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 the prototype property of the parent function. prototype_function

Property Lookup

  • 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 prototype

    abc.prototype.fname = 'aakash';
    
  • If we try to print fname property on our foo object, we get the output as aakash even though we didn't set any property on foo object itself

    foo.fname; => // 'aakash';
    foo.__proto__.fname; => // 'aakash';
    
  • What happens behind the scene is - On giving command foo.fname JavaScript engine ask foo object whether it has any property named fname, foo says NO, so JavaScript engine refers to foo parent i.e. abc.prototype via foo.__proto__ and fetches the property from abc().

  • 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 then foo.__proto__, we see both have same properties

    abc.prototype // {fname: "aakash", lname: "goplani", constructor: ƒ}
    foo.__proto__ // {fname: "aakash", lname: "goplani", constructor: ƒ}
    abc.prototype === foo.__proto__ //true
    

    property look-up

  • 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 on foo object, and if that property was present in foo 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.


Object Behavior

  • 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
    

Object Links

  • When we create objects from functions there are multiple references that are created behind the scenes:
    1. prototype- every function gets a new property that is an object named prototype. This will be shared by all the child objects that will be created using this function
    2. __proto__ - dunder proto property is assigned to every object (including functions) that points to prototype property of parent object
    3. constructor - every prototype object created has a reference constructor that points to the parent object.

object links

  • 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;
    }
    
  • 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() {}
    

Object Function

  • 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
    

Prototype Object

  • When we created new objects using constructor function, a property __proto__ gets assigned to newly created objects and it points to prototype property of parent function.

  • In other words, objects are only created when we use new or literal notation, so how does prototype object gets created when you write a constructor function? Answer is: it is automatically created using new 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 till Object.protoype

  • Now Object.protoype also has __proto__ which point to null - that marks end of hierarchy!

object prototype hierarchy


Inheritance

  • 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 have getName() 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 and dept and we have getDept() 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 because getName() 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.

    object prototype Inheritance 1

  • 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 access getName() 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"
    

    object prototype Inheritance 2


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)
    

protoype graph


Changing Prototype Reference

  • Now a points to the prototype of abc. If we change the prototype reference of abc to point to a new object

    abc.prototype = { name: 'xyz' };
    
  • Now a.__proto__ is not equal to abc.prototype. a.__proto__ holds value test whereas abc.prototype holds value xyz.

  • If we create new object from abc now

    var b = new abc();
    console.log(abc.prototype === b.__proto__) // true
    console.log(a.__proto__ === b.__proto__) // false
    
  • a and b 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.

protoype graph


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 to null

    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
  1. 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.

  2. 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 use new 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 by Object.create()

  3. Student.prototype.constructor = Student; => Every prototype has a constructor property which points to the function that created the object. So initially Student.prototype.constructor will point to Student function but as soon as we execute // line 2, the constructor property will now point to Person function. to make sure it correctly points to Student we write this line.