Prototypes

  • Objects built using prototype reference the common properties and methods that is stored in that prototype object rather than storing their own separate copies.
  • It is good to store functions and literals in prototype since they are not dynamically changing
function Time(hours, minutes) {
    this["hours"] = hours;
    this.minutes = minutes;
    Time.Now = "12:20" // static field
    this.printLog = function() {  // better to define in prototype
        console.log("I am logging Time");
    }
}
 
// defining fields using prototype
// These fields are accessible to only objects of Time
Time.prototype.seconds = "45";
Time.prototype.printTime = function() {
    console.log("12:20");
}
 
var time = new Time(12, 20);
console.log(time);
console.log(time.__proto__.seconds); // prints 45, __proto__ is accessible

[[Prototype]] vs prototype vs __proto__

  • Every object in JS has internal property called [[Prototype]] which can be accessed via different ways:
    • prototype is available to constructor function while it is not available to its corresponding instance.
    • __proto__ is available to object instances which can be accessed and points to the prototype
  • Access prototype chain
// we can chain prototype
obj.__proto__.__proto__
  • Modern JS way to get/set prototype:
    • Object.getPrototypeOf(obj)
      • returns the [[Prototype]] of obj.
    • Object.setPrototypeOf(obj, proto)
      • sets the [[Prototype]] of obj to proto.
    • Object.create(proto, [descriptors]
      • create an object with  [[Prototype]] of obj to proto.

Default prototype

  • When we define an object literal or array using square brackets, the [[Prototype]] automatically points to Object.prototype or Array.prototype
// Object literal
let obj = {
	a: 1
};
console.log(obj.__proto__ === Object.prototype);
 
// Array
let arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype);

Native Wrapper prototypes

  • We can add custom properties to Native wrapper prototype
  • But it is highly discouraged
  • It is used only in one situation: polyfills, where we want to support some latest methods in wrapper class
// if there's no such method, add it to the prototype
if (!String.prototype.repeatString) {
  String.prototype.repeatString = function(n) {
    // repeat the string n times
	// simple implementation
    return new Array(n + 1).join(this);
  };
}
 
console.log("La".repeatString(4)); // LaLaLaLa

Objects without __proto__

  • The [[Prototype]] can be either null or some object but cannot be some other primitive datatype like string:
let obj = {};
 
let key = "__proto__"; // dynamically choosing key maybe via input
obj[key] = "some value"; // This fails since prototype must be null or object
 
console.log(obj[key]); // [object Object], not "some value"!
  • To avoid the above issue, we can use either Map or create objects without prototype:
// Object.create(null) creates object with prototype as null
let obj = Object.create(null);
 
let key = "__proto__"; // dynamically choosing key maybe via input
obj[key] = "some value"; // This passes
 
console.log(obj[key]); // [object Object], not "some value"!