I’ve been reading a few articles about closures lately, and when I get to the bottom of them, I realize that most of them are a little bit more complicated.
In fact, at first I thought closures were a very high, very incomprehensible knowledge, but it is not, if only the description of the very official definition and the huge closure article progress bar, it was initially discouraged π°. I’m going to try to describe this knowledge point with examples as easily as possible. OK, here we go!
Before you begin, if you are not clear on the concepts of scope, execution context, and lexical environment, you are recommended to read: thoroughly understand JavaScript scope and scope chains π, thoroughly understand scope, execution context, and lexical environment π, otherwise part of this article may not be very friendly to you π€
define
First, let’s throw out the MDN definition of closure:
A combination of a function bound to (or surrounded by) references to its surrounding state (lexical environment) is a closure. That is, closures allow you to access the scope of an outer function within an inner function. In JavaScript, whenever a function is created, the closure is created at the same time the function is created.
OK, it doesn’t matter if you don’t understand the above mess. Let’s take a look at an example:
Example 1: Return a function within a function
function makeFunc() {
var name = "Mozilla"
function displayName() {
alert(name)
}
return displayName
}
var myFunc = makeFunc()
myFunc() // Mozilla
Copy the code
MakeFunc () in var myFunc = makeFunc() creates the corresponding function execution context in which a local variable name is declared and a function displayName is declared, finally returning a pointer (or reference) to the displayName function, Where the function displayName () {alert (name); } returns the myFunc variable
After var myFunc = makeFunc() is executed, the corresponding function execution context is popped from the stack. Normally, its variables are destroyed, but myFunc() calls myFunc, Function displayName(){alert(name); }, where name refers to the variable name in makeFunc, so its variable is not destroyed along with it, which encapsulates a private variable.
That’s the closure.
OK, moving away from the concrete example, let’s describe the concept of closures in human terms:
A closure is a function that refers to a variable inside another function. Because the variable is referenced, it is not recycled when the execution context of the other function ends and the corresponding execution context is popped on the stack, so it can be used to encapsulate a private variable. This is both a strength and a weakness; unnecessary closures only increase memory consumption because unused variables are not recycled in time.
More serious description:
Closures are a combination of functions and the lexical environment in which they are declared. The lexical environment contains any local variables that were scoped when the closure was created. (also from MDN)
OK, going back to the above example, the function makeFunc and its lexical environment (containing the variable name and the function displayName) are called a closure. The name variable in the lexical environment is referenced and will not be destroyed.
Let’s look at a similar but slightly different code:
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
add5 = null // Release the reference to the closure
console.log(add5(1)) //Uncaught TypeError: add5 is not a function
Copy the code
In this example, we define the makeAdder(x) function, which takes an argument x and returns a new function. The returned function takes a parameter y and returns the value of x+y.
In essence, a makeAdder is a function factory-it creates a function that adds and sums the specified value and its arguments. In the example above, we created two new functions using the function factory – one summing its arguments with 5 and the other with 10.
Add5 and add10 above are closures. They share the same function definition, but preserve different lexical environments (variable X). In the add5 environment, x is 5. In add10, x is 10.
Example 2: Execute the function immediately
var add = (function () {
var counter = 0;
return function () {
return counter += 1;
}
})();
add();
add();
add(); / / 3
Copy the code
Function () {return counter += 1; Function () {return counter += 1; } But it does not declare the variable counter, which was declared in the immediate function. When the immediate function completes, the corresponding execution context of the function is popped from the call stack and the variable counter is destroyed, but we can normally execute add() code output 3. The counter variable is not destroyed, which is also an indication of the closure.
Example 3: Timer printing problem
for(var i = 0; i < 5; i++) {
setTimeout(() = > {
console.log(i)
}, i * 1000)}Copy the code
This code is supposed to print 0, 1, 2, 3, 4 every second, but it actually prints 5, 5, 5, 5, 5.
In the above code, the variable I is declared by the var command and is valid globally, so there is only one variable I globally. In each cycle, the value of variable I overwrites the value of the previous cycle.
The callback to the timer function above is executed at the end of the loop. The loop ends with the condition that I is no longer < 5, and the value of I was 5 when the condition was first held, so the output shows the final value of I at the end of the loop. So it prints a 5 each time.
for(var i = 0; i < 5; i++) {
(function(j){
setTimeout(() = > {
console.log(j)
}, j * 1000)
})(i)
}
Copy the code
Change the code to the above and it will work the way we want it to. After this modification, using IIFE (execute function now) within each iteration generates a new scope for each iteration, allowing the callback of the delay function to enclose the new scope within each iteration, with a variable with the correct value accessible within each iteration.
The entire immediate-execute function above is the closure, and the variable j is part of the closure. At the end of the immediate-execute function, the variable J is referenced by the timer.
for(let i = 0; i < 5; i++) {
setTimeout(() = > {
console.log(i)
}, i * 1000)}Copy the code
Using ES6 block-scoped lets instead of var also serves our purpose.
In the above code, the variable I is declared by let, and the current I is valid for this loop, so each loop I is actually a new variable, so the final output is 0, 1, 2, 3, 4. You might ask, if the variable I for each cycle is redeclared, how does it know the value of the previous cycle and thus calculate the value of the current cycle? This is because the JavaScript engine internally remembers the value of the previous cycle, and when it initializes the variable I of this cycle, it evaluates on the basis of the previous cycle.
Let’s look at a similar code:
var data = []
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i)
}
}
data[0]()
data[1]()
data[2] ()/ / 3 3 3
// Use closures
var data = []
for (var i = 0; i < 3; i++) {
data[i] = (function (j) {
return function () {
console.log(j)
}
})(i)
}
data[0]()
data[1]()
data[2] ()/ / 0 1 2
/ / use the let
var data = []
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i)
}
}
data[0]()
data[1]()
data[2] ()/ / 0 1 2
Copy the code
OK, I believe you guys should have no problem seeing this. In addition to the top three examples of closures, let’s take a look at some other scenarios where closures are also used
Several other types of scenarios that use closures
1. Functions as arguments
var a = 'Rocky'
function foo(){
var a = 'foo'
function fo(){
console.log(a)
}
return fo
}
function f(p){
var a = 'f'
p()
}
f(foo()) // foo
Copy the code
Look at foo() in f(foo()). When it’s done, the variable a in f(foo() is not recycled because it’s referenced by fo(), so foo() and its lexical environment are closed.
2. Using a callback is using a closure
window.name = 'Rocky'
setTimeout(function timeHandler(){
console.log(window.name);
}, 100)
Copy the code
3. Throttling and anti-shaking
/ / throttling
function throttle(fn, timeout) {
let timer = null
return function (. arg) {
if(timer) return
timer = setTimeout(() = > {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
/ / image stabilization
function debounce(fn, timeout){
let timer = null
return function(. arg){
clearTimeout(timer)
timer = setTimeout(() = > {
fn.apply(this, arg)
}, timeout)
}
}
Copy the code
The role of closures
OK, finally, to summarize the function of closures (excerpt from the classic use scenario of JS closures and including closures must brush questions)
- Protects the function’s private variables from external interference. Form undestroyed stack memory.
- Save, to save the values of some functions. Closures can privatize methods and properties
Finally, I am a self-taught front end soon of non division class rookie, there must be a lot of description inappropriate place, welcome to point out, common discussion π
reference
What is a closure
Previous JS series articles
Full understanding of JavaScript scope and scope chain π Thoroughly understand scope, execution context, lexical environment π select 30+ cases and 10 questions to thoroughly master thisπ article summary: Promise/async/awaitβ¨