🌰 : One day, the director informs you that you need to display a browsing time on the company page, starting from the moment you open the page and adding one for each second.

All right, take the job. Let’s do the analysis

The essence of the business is to implement an accumulator that increments every second. All right, so let’s do it in turn.

First of all, the simplest “add one every second”, we immediately think of setting a timer to call the accumulator every second, assuming the accumulator function is called counter, write down the code with a clear idea.

setInterval(function () {
  // Call the accumulator
  counter();
}, 300);
Copy the code

Now that we’ve written the logic to call the accumulator, we need to define a variable to hold the number of seconds since we need to display the number of seconds. And incrementing it in the accumulator. Continue with the complete code.

/ / the number of seconds
let second = 0;

/ / accumulator
function counter() {
  second += 1;
}

setTimeInterval(function () {
  // Call the accumulator
  counter();
}, 300);
Copy the code

We’ve already written the business-critical code, and since the point of this article is to get you to understand closures, we won’t write the code that displays the number of seconds on the page. To verify that the code is correct, we can print the second value at the end, and to not waste space, we will add a temporary condition during the test to stop the accumulation at 10 seconds and modify our code

/ / the number of seconds
let second = 0;

/ / accumulator
function counter() {
  second += 1;
  return second;
}

const recordSecond = setInterval(function () {
  // Stop after 10 seconds
  if (second === 10) {
    clearInterval(recordSecond);
    console.log('End of time! ');
    return;
  }
  // Call the accumulator to output the current number of seconds
  console.log(`${counter()}Second `);
}, 1000);
Copy the code

We tested the code on the console and the results were as follows:

You can see that we’ve implemented what we need, which is the lowest level of implementation.

The reason why we say lowest level is because the idea is to define a global variable that is updated every time we add it, and there is an unwritten rule in all programming languages ———— to define as few global variables as possible.

There are roughly two reasons, one is difficult to control, two memory occupancy.

1. Global variables are not easy to control and can be read and written anywhere, meaning they can be rewritten by irrelevant programs.

2. Global variables occupy a long life cycle of memory. Generally, local variables (defined in a function) will be pushed out of the execution stack after the completion of the function call. Executing the environment out of the stack is telling the collection mechanism “I don’t need these variables, I can reclaim them”. Since global variables can be read and written anywhere by any program at any time, it is difficult for the reclamation mechanism to calculate when the memory occupied by global variables should be freed. As a result, global variables are usually freed only when the global execution environment is destroyed.

With that said, all we need to do is implement the functionality without polluting the environment by defining global variables.

Following the code above, the only change we need to make is to change second, which is defined globally, to a local variable to protect it from other operations. Think about where to put the second declaration…

Since we are going to define second as a local variable, let’s optimize the code to write the read and write of second as much as possible inside a function for easy maintenance.

First, put the condition to stop the scheduled task in the counter function,

/ / the number of seconds
let second = 0;

/ / accumulator
function counter() {
  // Stop after 10 seconds
  if (second === 10) {
    clearInterval(recordSecond);
    console.log('End of time! ');
    return;
  }
  second += 1;
  console.log(`${second}Second `);
}

const recordSecond = setInterval(function () {
  // Call the accumulator to output the current number of seconds
  counter();
}, 1000);
Copy the code

We will test the code on the console to see if it works. The results are as follows:

Ok, now our code is a little cleaner than the first version. We only call the accumulator in the callback function of the scheduled task, leaving it up to the counter to control when to stop the accumulation and when to execute the accumulation.

Next, let’s go ahead and define second as a local variable, because the setInterval callback is executed every second. If second is declared in the callback function, it will be initialized every time the callback is called. So you can’t add it up.

Therefore, consider declaring second inside the counter function as follows

/ / accumulator
function counter() {
  / / the number of seconds
  let second = 0;
  // Stop after 10 seconds
  if (second === 10) {
    clearInterval(recordSecond);
    console.log('End of time! ');
    return;
  }
  second += 1;
  console.log(`${second}Second `);
}

const recordSecond = setInterval(function () {
  // Call the accumulator to output the current number of seconds
  counter();
}, 1000);
Copy the code

As you can see, since the call to counter is made in the callback function, it is impossible to avoid the initialization operation by simply defining second in counter.

Think about it, how do we break the counter into two parts, the initialization of the second and the operations related to the accumulation, and we modify the counter with that in mind

/ / accumulator
function counter() {
  / / the number of seconds
  let second = 0;

  function doCounter() {
    // Stop after 10 seconds
    if (second === 10) {
      clearInterval(recordSecond);
      console.log('End of time! ');
      return;
    }
    second += 1;
    console.log(`${second}Second `); }}const recordSecond = setInterval(function () {
  // Call the accumulator to output the current number of seconds
  counter();
}, 1000);
Copy the code

As you can see, from the structure of the code, we clearly divide counter into two parts. Because doCounter is an internal function of counter, it has access to variables in the outer function scope.

However, there is a problem with the current code. We just put the cumulative operations in a function, but we don’t return the function, so even if counter is called, it only declares its internal variable second and function doCounter.

Simply, we make doCounter the return value of counter, so that when counter is called, doCounter is called. Change the code as follows

/ / accumulator
function counter() {
  / / the number of seconds
  let second = 0;

  function doCounter() {
    // Stop after 10 seconds
    if (second === 10) {
      clearInterval(recordSecond);
      console.log('End of time! ');
      return;
    }
    second += 1;
    console.log(`${second}Second `);
  }

  return doCounter;
}

const recordSecond = setInterval(function () {
  // Call the accumulator to output the current number of seconds
  counter();
}, 1000);
Copy the code

We copy the code to the console to test, and, oh, strange thing happened, the console actually did not output any information…

Prove that doCounter is not executed correctly. Look at the code and see where the problem is.

Let’s focus on the return part of counter,

/ / accumulator
function counter() {
  / / the number of seconds
  let second = 0;

  function doCounter() {
    // Stop after 10 seconds
    if (second === 10) {
      clearInterval(recordSecond);
      console.log('End of time! ');
      return;
    }
    second += 1;
    console.log(`${second}Second `);
  }

  return doCounter;
}
Copy the code

We know that the callback to the scheduled task must be executed if the destruction condition is not met, so the call to counter must also be executed.

We use doCounter function as the return value of counter function, so we can get the return content of counter function by calling counter function, here is the problem!!

We’re just returning doCounter, and the call to counter is just returning doCounter, but doCounter is not being called anywhere.

To improve readability, we assign a variable called doCounterFn to what is returned from calling counter. DoCounterFn is the function that actually does the summation-related operations, and that’s what the callback function in setInterval really needs to care about. Go ahead and modify the code as follows

/ / accumulator
function counter() {
  / / the number of seconds
  let second = 0;

  function doCounter() {
    // Stop after 10 seconds
    if (second === 10) {
      clearInterval(recordSecond);
      console.log('End of time! ');
      return;
    }
    second += 1;
    console.log(`${second}Second `);
  }

  return doCounter;
}

// Get the accumulator
const doCounterFn = counter();

const recordSecond = setInterval(function () {
  // Call the accumulator
  doCounterFn();
}, 1000);
Copy the code

A concept, when we will be a function call return values assigned to a variable, this function returns if the call is a function of the variable is the reference for this return function of memory address (returning function), when a function is referenced, the relationship between garbage collection mechanism is not recycled it accounts for memory, That is, the memory occupied by the function’s entire execution context (variable objects, scope chains, etc.) is kept. Because the returned function is also an internal function of a function, the scope is nested, forming a closure. The scope chain of the returned function includes the scope of the outer function, that is, the return function can access the variables defined in the outer function.

Therefore, when we call doCounter indirectly through doCounterFn, even though the variable second does not exist on the scope chain of doCounterFn, doCounter is executed and still has access to its variables on the scope chain. That is, any variable in the scope in which it is declared. This is a typical example of scope extension.

By nesting counter and doCounter functions, you form a nesting of scopes. The nested function needs to access the scope in which it is nested, and then the nested function needs to be called in another scope. This whole process is called closure.

Let’s run the above code and get the following results:

So far we’ve fulfilled the requirement relatively perfectly,

Through this example, we also make an understanding of the process of closure formation. Finally, we make a summary of closures:

1. When do you need closures

When we need to reuse an object, but want to protect the object from other code contamination.

2. What closures do

Gives an external function access to an internal function scope.

3. Prerequisites for closure formation

  1. The scope needs to be accessed
  2. Function nesting (physical condition)
  3. The nested function is called in another outer scope

4. Disadvantages of closures

It is recommended that you manually assign an empty flag to reclaim fn = null after use.

That’s the end of this article, can stick to see the small partners will become a big talent!