This is the sixth day of my participation in Gwen Challenge
Relearn JavaScript in a series of articles…
Normally, after a function is defined, a function scope is created, and local variables in the function body can only be used in the function scope. When the function completes, the space occupied by the function is reclaimed, and the local variables in the function are also reclaimed and cannot be accessed. If we want local variables in a function to still be accessible, we need to use closures.
Let’s look at a classic example of using closures:
In the following code example, no matter which div we click on, the output will be the value of D.O.M.length. // This is because the for loop has already ended before we trigger the click event, at which point I is d.O.M.length. var doms = document.querySelectorAll('div') for(var i=0; i<doms.length; Function (){console.log(I)}} Article will closures, here with var doms closure way = document. QuerySelectorAll (' div ') for (var I = 0; i<doms.length; I ++){(function(I){doms[I].onclick = function(){console.log(I)}})(I)}Copy the code
Before we talk about closures, let’s take a look at the execution context.
Execution context
Each piece of code in JS has an execution context, and any execution context exists in the overall execution context. The execution context generated by the global environment is pushed first and exists at the bottom of the stack. When a new function is called, a new execution context is created and pushed onto the stack. When the function call completes, both the context and the data in it are destroyed and the stack is popped to the previous execution context.
Note that only one execution context can be active. See how the execution context changes through the code:
Var foo = function(y){console.log(y)} var boo = function(x){var y = 10 foo(x+y) // 3. Execute foo execution context} boo(10) // 2. Enter the boo execution contextCopy the code
From line 1, we enter the global execution context, which only exists in the global execution context, and push it to the bottom of the stack.
At line 9, boo() is called and the boo() execution context is added to the stack for the currently active execution context.
At line 7, foo() is called and fo0() is added to the stack as the currently active execution context. When foo() is finished, the execution context is destroyed. Return to the execution context of the bar() function, continue to execute the code, and then exit the stack for destruction. Finally, the global context completes, the stack is cleared, and the process finishes.
If the execution context cannot be cleanly destroyed when the code is finished executing, this is what we call a closure.
closure
There is a general official explanation for closures:
An expression, usually a function, that has a number of variables and the environment in which those variables are executed.
Closures have two distinct characteristics:
-
A reference to an external variable owned by the function that is still active when the function returns.
-
When a closure returns as a function, its execution context is not destroyed and remains in the execution context.
Here’s an example:
function fn(){
var max = 1
return function bn(x){
if(x>max){
console.log(x)
}
}
}
var f1 = fn()
f1(2)
Copy the code
When the code starts executing, the global execution context is generated and pushed to the bottom of the stack.
When line 9 is executed, the fn() function is called, the fn() function context is generated, and pushed onto the stack, returning the bn() function, and assigning to f1.
When line 10 is executed, f1() is called, because f1 contains a reference to Max, and the Max variable exists in fn, so the fn function execution context is not destroyed directly, but still exists in the execution context.
When line 10 completes, the bn function execution context is destroyed, the Max variable reference is released, and the fn function execution context is destroyed.
Finally, the global context completes, the stack is cleared, and the process finishes.
As we can see from our examples, the biggest problem with closures is memory consumption!
Based on the characteristics of closures, we can use the features of closures to implement some features:
1. Result caching
If the function call takes time to process, we can cache the result in memory. If the result exists in memory, it will be returned directly to improve the execution efficiency.
Let cacheObj = function(){let cache = {} return {search:function(key){if(key){ Let result = dealFn(key) // Update cache result cache[key] = result return result}} let cacheBox = cache() cacheBox.search(1)Copy the code
Second, the encapsulation
The idea of modularity is to encapsulate attributes that have a certain specificity, and only expose the corresponding function externally, without caring about the internal implementation.
let stack = function(){
let stack = []
return {
push(val){
stack.push(val)
},
pop(){
return stack.pop()
}
}
}
Copy the code
summary
The reasonable use of closures can improve the efficiency of code execution to a certain extent. If used improperly, memory is wasted and performance deteriorates. Summarize the pros and cons of closures.
Advantages:
-
Contains the security of variables within the function, to achieve encapsulation, to prevent variables from flowing into other environments, naming conflicts, resulting in environmental pollution.
-
When appropriate, variables can be maintained in memory and cached to improve execution efficiency.
Disadvantages:
Consuming memory: Normally, a function’s live object is destroyed along with the context, but since a closure refers to an external function’s live object, this live object cannot be destroyed because closures consume more memory than normal functions.
So far we’ve looked at the features, pros and cons, and uses of closures.