DEV Community

Cover image for Advanced JavaScript Series - Part 8: The 2 Pillars~ Closures & Prototypal Inheritance
Pranav
Pranav

Posted on • Updated on

Advanced JavaScript Series - Part 8: The 2 Pillars~ Closures & Prototypal Inheritance

Closures-

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

Image description Credits- Edward Huang

  • Lets understand concept of closures with the help of examples.
  • Closures have two major advantages.

1. Memory efficient

Example 1-

  • We want to build a counter function that keeps a track of counts and the count gets increased on calling the function. For that we'll need a count variable initialized to zero.
  • But we don't want it to be accessed by anyone else and alter it thus we don't want the count variable to be in a global scope for this very reason.
  • We can't declare it inside the function either because whenever the function will be called, it will create a new execution context that creates a new local scope for the function(we have learned this in previous parts of our series). Thus the count variable gets reinitialized to zero every time we call the function, hence we can't declare it in local/functional scope either.
  • We can also try to use nested functions just like this-
function add() {
  let counter = 0;
  function plus() {counter += 1;}
  plus();   
  return counter;
}
Enter fullscreen mode Exit fullscreen mode

But here we can't call the plus() function from outside hence this is of no use.

  • Here comes the concept of closures and self invoked functions(learned in earlier parts of the series).

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

const add = (function () {
  let counter = 0;
  return function () {counter += 1; return counter}
})();

add();
add();
add();
Enter fullscreen mode Exit fullscreen mode
  • Here as you can see the function that we return from the self invoked function has a reference of a variable that is outside its local environment just like we said in closures- with references to its surrounding state.
  • These references from external environment are stored in the memory even if we lose the function outside because the particular reference is being used in the function we call.
  • That's why closures are very a powerful concept.

Example 2-

Code-
const getHeavy = heavy();
console.log(getHeavy(699))
console.log(getHeavy(700))
console.log(getHeavy(701))

// we don't want to pollute global namespace
function heavy() {
  const bigArray = new Array(7000).fill('hello')
  return function(item) {
    return bigArray[item]
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Here we are returning a function that can access the required index whenever called, without polluting our global namespace.
  • Here the reference to the array bigArray stays in memory even though the outer function is popped from the call stack and its context is removed because of the concept of closures and we are able to use the getHeavy function to access required indexes from it.

Output-

"hello"
"hello"
"hello"
Enter fullscreen mode Exit fullscreen mode

2. Encapsulation

  • We can make variables that are not accessible in the global scope by anyone or any function.
  • We can also make variables that are accessible via a function without it being in its local scope such that it gets destroyed when its execution context is popped of the call stack.
  • We can make variables encapsulated and safe with the help of closures.

Example-

Code-
const getHeavy = heavy();
console.log(getHeavy(699))
console.log(getHeavy(700))
console.log(getHeavy(701))

// we don't want to pollute global namespace
function heavy() {
  const bigArray = new Array(7000).fill('hello')
  return function(item) {
    return bigArray[item]
  }
}
Enter fullscreen mode Exit fullscreen mode
  • The bigArray cannot be accessed from anywhere in the function except for the function that we return to the getHeavy variable.
  • In this way the array is encapsulated, we can access it anytime, from anywhere without it being declared in the global namespace/scope and this is property is very useful in different scenarios.

Image description Credits- Neelesh Vishwakarma

Prototypal Inheritance-

Prototypal Inheritance is a method by which an object can inherit the properties and methods of another object.

  • All JavaScript objects inherit properties and methods from a prototype.
  • Date objects inherit from Date.prototype
  • Array objects inherit from Array.prototype
  • Person objects inherit from Person.prototype
  • The Object.prototype is on the top of the prototype inheritance chain:

  • Date objects, Array objects, and Person objects inherit from Object.prototype.

  • And if we check the prototype of the Object then we see null being returned by JavaScript since Object is root element in JS.

Inheritance
Credits- Ryan Thelin

  • __proto__ is another keyword that can help us determine the parent/prototype of any object(even array or function) in javascript.

Let's see this with help of an example-

Example 1-

  • Let's make an object for a phone that would have all the basic properties that a phone should have.
  • Then we would make an Object for an iPhone, that would inherit the properties from the generic phone object to specify all the basic features and then add its own specific features to the iPhone object(itself).

  • We also have a isPrototypeOf() method that checks if an object exists in another object's prototype chain.

The hasOwnProperty() method returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it).

Code-

const phone = {
  calling: true,
  camera: true,
  touchscreen: true,
}

const iphone = {
  software: "IOS",
  security: "Face Unlock",
}

iphone.__proto__ = phone
console.log(iphone.calling)
console.log(phone.isPrototypeOf(iphone))
console.log(phone.hasOwnProperty(camera))
Enter fullscreen mode Exit fullscreen mode
  • In this example, when run console.log(iphone.calling), the JS engine checks iphone's properties and looks for the key calling.
  • When we use prototypal inheritance, the properties don't get added to the child object itself. That is why, when we access a property that is not there in the child object, the JS engine continues its searches up the prototype chain in parent object's properties and returns if found.
  • If not found, undefined is logged on the console.
  • This above is the reason, false is returned, when we run console.log(phone.hasOwnProperty(camera)) because the iphone object doesn't have the camera property natively, instead it is inherited from the prototytpe.

Output-

true
true
false
Enter fullscreen mode Exit fullscreen mode

Example 2-

  • __proto__ always returns the parent object of our current object that it inherits its properties from.
  • If we take an array or a function and access __proto__ property of either one, firstly we'll see their respective objects in the output.
  • But if we further access the __proto__ property of their outputs then we get the constructor object "Object" that is the base unit of arrays, functions, objects etc in JavaScript.
  • We cannot go any further back than the Object property. Behind that we only receive null.

Code-

const phone = {
  calling: true,
  camera: true,
  touchscreen: true,
}

const iphone = {
  software: "IOS",
  security: "Face Unlock",
}

iphone.__proto__ = phone
console.log(iphone.__proto__) // we recieve phone object
console.log(iphone.__proto__.__proto__) // we get the base constructor object
console.log(iphone.__proto__.__proto__.__proto__) // we get null here since we cannot go further back than an Object which is base unit
Enter fullscreen mode Exit fullscreen mode

Output-

Output1

  • prototype keyword in JavaScript is always present in the parent object that holds all the properties that would be inherited down to its child. It also holds the parent object's own __proto__ property to access its parent.

Example to help understand-

Code-

const phone = {
  calling: true,
  camera: true,
  touchscreen: true,
}

const iphone = {
  software: "IOS",
  security: "Face Unlock",
}

iphone.__proto__ = phone
console.log(iphone.prototype)
Enter fullscreen mode Exit fullscreen mode
  • Traditionally, in order to get and set the [[Prototype]] of an object, we use Object.getPrototypeOf and Object.setPrototypeOf. Nowadays, in modern language, it is being set using __proto__.

  • One reason to use the built-in prototype object is if you'll be duplicating an object multiple times that will share common functionality. By attaching methods to the prototype, you can save on duplicating methods being created per each new instance.

  • __proto__ is an object in every class instance that points to the prototype it was created from.

  • The only true difference between prototype and __proto__ is that the former is a property of a class constructor, while the latter is a property of a class instance.

  • __proto__ is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build __proto__.

  • Updating the __proto__ property is not a good practice, instead a good way to inherit properties is by using Object.create().

Another way of creating a prototype chain Object.create()

ECMAScript 5 introduced a new method: Object.create(). Calling this method creates a new object. The prototype of this object is the first argument of the function.

Example-

Code-

const phone = {
  calling: true,
  camera: true,
  touchscreen: true,
}

const iphone = Object.create(phone)

iphone.software= "IOS",
iphone.security= "Face Unlock"

console.log(iphone.calling)
Enter fullscreen mode Exit fullscreen mode

Output-

true
Enter fullscreen mode Exit fullscreen mode

Some useful articles-

Tricky example to test knowledge about prototype chain-

Code-

const multiply = function(a, b){
  return a*b
}

console.log(multiply.__proto__)
console.log(Function.prototype)
console.log(multiply.__proto__.__proto__)
console.log(Object.prototype)

console.log(typeof Object)
console.log(typeof Object.prototype)
Enter fullscreen mode Exit fullscreen mode

Output-

Function constructor
Function constructor
Object constructor
Object constructor
'function'
'object'
Enter fullscreen mode Exit fullscreen mode
  • Object is an inbuilt function in JavaScript. It also has a prototype of its own like all the other functions in JS.
  • Object.prototype returns an 'object' as output since the base element/parent of a function is the object constructor in JavaScript. (as we learned before)

Connect with me-


Appendix-

  1. Advanced JavaScript Series - Part 1: Behind the scenes (JavaScript Engine, ATS, Hidden Classes, Garbage Collection)
  2. Advanced JavaScript Series - Part 2: Execution Context and Call Stack
  3. Advanced JavaScript Series - Part 3: Weird JS behavior, Strict Mode and Hoisting, Temporal Dead Zone
  4. Advanced JavaScript Series - Part 4.1: Global, Function and Block Scope, Lexical vs Dynamic Scoping
  5. Advanced JavaScript Series - Part 4.2: Scope Chains and their working, Lexical and Variable Environments
  6. Advanced JavaScript Series - Part 5: IIFE & 'this' keyword in JS(tricky Eg.), call(), apply(), bind(), Currying(Functional Prog)
  7. Advanced JavaScript Series - Part 6.1: Everything in JS is an Object? Weird JS behaviors revealed, Primitive Non-Primitive Types
  8. Advanced JavaScript Series - Part 6.2: Pass by Value & Pass by Reference, Shallow & Deep Copy, Type Coercion
  9. Advanced JavaScript Series - Part 7: First Class Citizens & Higher Order Functions
  10. Advanced JavaScript Series - Part 8: The 2 Pillars~ Closures & Prototypal Inheritance
  11. Advanced JavaScript Series - Part 9: Constructor Functions, Object Oriented, new keyword

References-

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
  2. https://www.geeksforgeeks.org/prototypal-inheritance-using-__proto__-in-javascript/
  3. https://javascript.plainenglish.io/proto-vs-prototype-in-js-140b9b9c8cd5
  4. https://stackoverflow.com/questions/4736910/javascript-when-to-use-prototypes
  5. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf
  6. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

Discussion (0)