preface
A few days ago, I saw a popular article on digging gold “the days when I was an interviewer in Cool Kale”. The author of this article elaborates on what skills you should have as a Web candidate to be more marketable from an interviewer’s perspective.
In the process of comparing myself, I found some problems, maybe understand, but not comprehensive, which is also the reason for the birth of this series of articles.
What is a closure
A closure is a combination of a function and the lexical environment in which it is declared. (from MDN)
The official explanation is usually very difficult to say, colloquially speaking:
A closure creates a context that contains all local variables that can be accessed at creation time. And it is not affected by GC.
The simplest closure
Let’s start with the simplest closure declaration:
function func1() {
// func1 Is a local variable of a function
var msg = 'hello world! '
// A function created inside a function can access its parent function's variables according to its scope chain
Func2 is a closure in this code.
function func2() {
console.log(msg)
}
func2()
}
func1() => hello world
Copy the code
This is a normal method call, isn’t it? In real code, often written this way, nothing special has been found.
Func2 is, in effect, a closure that follows the definition and internal concept of a closure and, in the context of func2, contains all the local variables – MSG that were accessed at the time it was created, and MSG is not affected by GC.
Advanced representations of closures
Let’s look at another piece of advanced code to verify why func2 is a closure:
function func1() {
var msg = 'hello world! '
function func2() {
console.log(msg)
}
return func2
}
var func = new func1()
func()
Copy the code
Compared to the previous example, simply call func2() => return func
What is the output of func()? Actually, “Hello world!” .
Let’s go back to the definition of closures:
A closure creates a context that contains all local variables that can be accessed at creation time. And it is not affected by GC.
We try to understand that func2 is a closure, and when func2 was created, the environment had access to the local variable created at that time. When func1 was executed, the closed environment was returned, and the closed environment was unaffected by GC and still had access to the MSG variable.
Take it a little bit further
function func1(p1) {
return function func2(p2) {
return p1 + p2
}
}
var f1 = new func1(1)
var f2 = new func1(10)
f1(1) = >2
f2(10) = >20
Copy the code
In the above code, f1 and F2 are closures, and although they have the same function definition, they actually have different contexts:
Function f1 => 1 + 1 => 2
Function f2 => 10 + 10 => 20
The role of closures
From the above example, we know the concept and definition of closures. So what do closures actually do?
Closures, as I understand them, are meant to solve the problem of variable values on the chain of functions’ scopes.
There are a few classic applications:
The function is currified
/* * Function Currification can effectively decouple code * thanks to the closed context of closures * in f1, F2, f3 instantiation objects, each with a different lexical context */
function f (x) {
return function (y) {
return function (z) {
return function (a) {
return function (b) {
return function (c) {
console.log(x + y + z + a + b + c);
};
};
};
};
};
}
var f1 = f(1)
var f2 = f(2)
var f3 = f(10) (10)
Copy the code
Simulate private methods
/* * Thanks to the closed context of closures, we have a different lexical context in each F1, F2 instantiation object * not only that, the function also extends private methods * * changeIndex is a private method, which is exposed inside the function and hidden from the outside of the function */
var func1 = function() {
var index = 0
// changeIndex is private
function changeIndex(val) {
index = index + val
return index
}
return {
increment: function() {
return changeIndex(1);
},
decrement: function() {
return changeIndex(- 1);
},
value: function() {
returnindex; }}};var f1 = new func1()
var f2 = new func1()
f1.value() => 0
f1.increment() => 1
f1.increment() => 2
f1.decrement() => 1
// compare f1 with F2
f1.value() => 1
f2.value() => 0
Copy the code
A classic problem
Circular reference problems with closures
/* * Expected loop output 0-9 * actual loop output 10 */
function f1(){
console.log('begin')
for (var index = 0; index < 10; index++) {
setTimeout((a)= > {
console.log(index)
}, 1000);
}
console.log('end')
}
f1()
Copy the code
In this classic problem of closure loops, we examine our understanding of closures, function scope, and event loops.
Let’s first try to understand how this function works through an event loop:
- The main thread executes the definition of function f1 and executes function f1.
- The main thread performs regular tasks => console.log(‘begin’)
- The main thread executes the for loop with the setTimeout function itself.
- Add the Task of the setTimeout callback function to the Task Queue.
- The main thread performs regular tasks => console.log(‘end’).
- The execution stack for the main thread is empty
- When the timer fires, the main thread retrieves the setTimeout Task and executes the corresponding callback (at this point, the for loop has long since ended and the index value has changed to 10).
- Print 10 times 10
So how to improve?
Use self-executing functions
/* * The actual loop output is 0-9 */
function f1() {
console.log('begin')
for (var index = 0; index < 10; index++) { ; (function(index) {
setTimeout((a)= > {
console.log(index)
}, 1000)
})(index)
}
console.log('end')
}
f1()
Copy the code
Use ES6’s Let solution
/* * The actual loop output is 0-9 */
function f1() {
console.log('begin')
for (let index = 0; index < 10; index++) {
setTimeout((a)= > {
console.log(index)
}, 1000)}console.log('end')
}
f1()
Copy the code
reference
-
MDN web docs
-
Learn Javascript closures
series
- One knowledge point a day