In depth explanation of closure closure in javascript

  • 2021-10-27 06:33:43
  • OfStack

Brief introduction

Closure closure is a very powerful feature in javascript. Closures are functions within functions, and inner functions can access the scope of outer functions, so they can be used to do some powerful work.

Today, we will introduce 1 lower closure in detail.

Functions within functions

We mentioned that a function in a function can access variables in the scope of the parent function. Let's look at an example:


function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 alertAddress();
}
parentFunction();

In the above example, we defined a variable address in parentFunction and an alertAddress method inside parentFunction, which accesses the address variable defined in the external function.

The above code runs without problem, and the data can be accessed correctly.

Closure closure

The function in the function has it, so what is a closure?

Let's look at the following example:


function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 return alertAddress;
}
var myFunc = parentFunction();
myFunc();

This example is very similar to the first example, except that we return the internal function and assign it to myFunc.

Next, we called myFunc directly.

The address variable in parentFunction is accessed in myFunc, although parentFunction has finished executing and returned.

However, when we call myFunc, we can still access the address variable. This is the closure.

This feature of closures is very well owned, and we can use closures to generate function factory, as follows:


function makeAdder(x) {
 return function(y) {
 return x + y;
 };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2)); // 7
console.log(add10(2)); // 12

Where add5 and add10 are closures, they are created by makeAdder, function, factory. By passing different x parameters, we get add methods with different cardinality.

Finally, two different add methods are generated.

Using the concept of function factory, we can consider the practical application of one closure. For example, we have three button on the page, and we can modify fonts by clicking on these button.

We can first generate three methods with function factory:


function makeSizer(size) {
 return function() {
 document.body.style.fontSize = size + 'px';
 };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

With these three methods, we bind the DOM element to the callback method:


document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

Implementing private method with closure

Compared with java, java has an private access descriptor, and with private, we can specify that the method only accesses inside class.

Of course, there is no such thing in JS, but we can use closures to achieve the same effect.


var counter = (function() {
 var privateCounter = 0;
 function changeBy(val) {
 privateCounter += val;
 }

 return {
 increment: function() {
  changeBy(1);
 },

 decrement: function() {
  changeBy(-1);
 },

 value: function() {
  return privateCounter;
 }
 };
})();

console.log(counter.value()); // 0.

counter.increment();
counter.increment();
console.log(counter.value()); // 2.

counter.decrement();
console.log(counter.value()); // 1.

We defined the privateCounter property and the changeBy method in the parent function, but these methods are only accessible in the internal function.

Through the concept of closure, we encapsulate these properties and methods, expose them to the outside, and finally achieve the effect of encapsulating private variables and methods.

Scope Chain for closures

For each closure, there is a scope, including the scope of the function itself, the scope of the parent function, and the global scope.

If we embed a new function inside the function, then a scope chain will be formed, which we call scope chain.

Look at the following example:


// global scope
var e = 10;
function sum(a){
 return function(b){
 return function(c){
  // outer functions scope
  return function(d){
  // local scope
  return a + b + c + d + e;
  }
 }
 }
}

console.log(sum(1)(2)(3)(4)); // log 20

Common problems with closures

The first common problem is using closures in loop traversal. Let's look at an example:


function showHelp(help) {
 document.getElementById('help').innerHTML = help;
}

function setupHelp() {
 var helpText = [
  {'id': 'email', 'help': 'Your e-mail address'},
  {'id': 'name', 'help': 'Your full name'},
  {'id': 'age', 'help': 'Your age (you must be over 16)'}
 ];

 for (var i = 0; i < helpText.length; i++) {
 var item = helpText[i];
 document.getElementById(item.id).onfocus = function() {
  showHelp(item.help);
 }
 }
}

setupHelp();

In the above example, we created an setupHelp function. In setupHelp, the onfocus method is given a closure, so item in the closure can access the item variable defined in the external function.

Because we assign values in the loop, we actually create three closures, but these three closures share the scope of the same external function.

Our intention is that different id triggers different help messages. However, if we really implement it, we will find that no matter which id, the final message is the last one.

Because onfocus is triggered after the closure is created, the value of item actually changes at this time. After the loop ends, the value of item has already pointed to the last element, so all the help messages of the last data are displayed.

How to solve this problem?

The simplest approach uses the let descriptor introduced in ES6, which defines item as the scope of block, and creates a new item each time, leaving the value of item in the closure unchanged.


 for (let i = 0; i < helpText.length; i++) {
 let item = helpText[i];
 document.getElementById(item.id).onfocus = function() {
  showHelp(item.help);
 }
 }

Another way is to create another closure:


function makeHelpCallback(help) {
 return function() {
 showHelp(help);
 };
}

 for (var i = 0; i < helpText.length; i++) {
 var item = helpText[i];
 document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
 }

Using the function factory concept we mentioned earlier, we create different scoped environments for different closures.

Another method is to include item in a new function scope, so that each time a new item is created, which is similar to the principle of let:


function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 return alertAddress;
}
var myFunc = parentFunction();
myFunc();
0

The second common problem is memory leakage.


function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 return alertAddress;
}
var myFunc = parentFunction();
myFunc();
1

In the above example, childFunction refers to the variable a of parentFunction. As long as childFunction is still in use, a cannot be released, resulting in parentFunction not being garbage collected.

The problem of closure performance

We define an object and access its private properties through closures:


function parentFunction() {
 var address = 'flydean.com'; 
 function alertAddress() { 
 alert(address); 
 }
 return alertAddress;
}
var myFunc = parentFunction();
myFunc();
2

What's wrong with the object above?

The problem with the above objects is that for every object that comes out of new, getName and getMessage methods will be copied once, one is the redundancy of content, and the other is the impact of performance.

Generally speaking, we define the methods of objects on prototype:


function MyObject(name, message) {
 this.name = name.toString();
 this.message = message.toString();
}
MyObject.prototype.getName = function() {
 return this.name;
};
MyObject.prototype.getMessage = function() {
 return this.message;
};

Note that we don't directly override the entire prototype, which will lead to unknown errors. We just need to override specific methods as needed.

Summarize

Closure is a very powerful and useful concept in JS. I hope you can like it.


Related articles: