Understanding closures in javascript functional programming (closure)

  • 2021-01-25 07:06:05
  • OfStack

Closures (closure) are a concept in functional programming that emerged in the 1960s. The first language to implement closures was Scheme, a dialect of LISP. The closure feature has since been widely adopted by other languages.
A closure is strictly defined as "a collection of functions (environments) and their enclosing free variables." This definition is a bit obscure to you, so let's start with examples and a less rigorous explanation of what a closure is, and then give examples of some of the classic uses of closures.

What are closures

In general, each function in JavaScript is a single closure, but nested functions are generally more robust
To demonstrate the nature of closures, consider the following example:


var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter = generateClosure();
console.log(counter()); //  The output  1
console.log(counter()); //  The output  2
console.log(counter()); //  The output  3

In this code, the generateClosure() function has a local variable, count, with an initial value of 0. There is also a function called get, which increases the count variable in its parent scope, the generateClosure() function, by 1 and returns the value of count. The return value of generateClosure() is the get function. Externally we call the function generateClosure() via the counter variable and get its return value, which is the get function. Then we call the function counter() several times and find that the return value is incremented by 1 each time.
Let's take a look at the above example. According to the usual imperative programming thinking, count is a variable inside the function generateClosure, and its life cycle is the period when generateClosure is called. When generateClosure is returned from the call stack, the space claimed by the variable count is freed. The problem is that after the call to generateClosure(), counter() references the "already released" count variable, and instead of making an error, it modifies and returns count each time counter() is called. What's going on here?
This is the property of so-called closures. When a function returns one of its internal defined functions, a closure is generated. The closure includes not only the returned function, but also the context in which the function is defined. In the example above, the local variable of counter and generateClosure() is a closure when the inner function get () is referenced by an external variable counter. If that's not clear enough, here's an example to help
You understand:


var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter1 = generateClosure();
var counter2 = generateClosure();
console.log(counter1()); //  The output  1
console.log(counter2()); //  The output  1
console.log(counter1()); //  The output  2
console.log(counter1()); //  The output  3
console.log(counter2()); //  The output  2

The above example illustrates how closures are generated: counter1 and counter2 call the generateClosure() function, respectively, to generate two instances of the closure, each of which internally references the count variable, each of which belongs to its own runtime environment. We can understand that when generateClosure() returns get, the internal variable (that is, the variable) of the function that get might refer to is also returned, and a copy is generated in memory. After that, the two instances of the function returned by generateClosure() are independent of each other.

The purpose of closures

1. Nested callback functions
Closures serve two main purposes: 1 to implement nested callback functions, and 2 to hide the details of objects. Let's look at the following code example to see the nested callback function. The following code is used in ES65en. js to implement a simple function to increase the user:


exports.add_user = function(user_info, callback) {
var uid = parseInt(user_info['uid']);
mongodb.open(function(err, db) {
if (err) {callback(err); return;}
db.collection('users', function(err, collection) {
if (err) {callback(err); return;}
collection.ensureIndex("uid", function(err) {
if (err) {callback(err); return;}
collection.ensureIndex("username", function(err) {
if (err) {callback(err); return;}
collection.findOne({uid: uid}, function(err) {
if (err) {callback(err); return;}
if (doc) {
callback('occupied');
} else {
var user = {
uid: uid,
user: user_info,
};
collection.insert(user, function(err) {
callback(err);
});
}
});
});
});
});
});
};

If you're not familiar with Node.js or MongoDB, that's fine. You don't need to understand the details, just look at the general logic. This code uses layers of nesting of closures, with each layer of nesting being a callback. The callback function is not executed immediately, but is called back by the requesting function after the corresponding request has been processed. As you can see, there is a reference to callback in each of the nested layers, and the innermost layer also uses the uid variable defined in the outer layer. Because of the closure mechanism, variables in the scope of the outer function will not be released even after the outer function has finished executing, because the inner function may still refer to those variables, thus perfectly implementing nested asynchronous callbacks.

2. Implement private members
As we know, JavaScript objects have no private properties, which means that every property of the object is exposed to the outside world. This may have security risks, such as the user of the object directly modifying a property, resulting in the object's internal data uniformity is broken, etc. By convention, JavaScript prefaces all private attributes with an underscore (for example, _myPrivateProp) to indicate that the attribute is private and should not be read or written directly by external objects. But this is just an informal convention, assuming that the user of the object doesn't do this. Is there a stricter mechanism? The answer is yes, through closures. Let's look at the previous example again:


var generateClosure = function() {
var count = 0;
var get = function() {
count ++;
return count;
};
return get;
};
var counter = generateClosure();
console.log(counter()); //  The output  1
console.log(counter()); //  The output  2
console.log(counter()); //  The output  3

As you can see, the count variable in the closure can only be accessed by calling counter() and incrementing it by 1 by the rule, and there is no other way to find the count variable. Inspired by this simple example, we can hide the details by wrapping an object in a closure that returns only one "accessor" object.

That's the end of this article, hopefully to help you understand javascript closures better.


Related articles: