preface

Repeat timer, JS has a method called setInterval specifically designed for this, but people diss it for many reasons, such as frame skipping, such as easy memory leaks, is no one’s favorite child. In addition, setTimeout can completely realize the effect of repeated timing through its own iteration, so setIntervval is more neglected and shuns it, feeling that setInterval is very low. But!!! Is setInverval really better than setTimeout? Please follow the author to explore step by step!

The outline

  • Problems with the repeat timer

  • Hand write a repeat timer

    • SetTimeout problems and optimization
    • SetInterval issues and optimization
  • SetInterval back in those years – prone to memory leaks

Various problems with repeat timers

Neither setTimeout nor setInterval can escape execution delay and frame skipping. Why is that? The JS Stack in the event ring is too busy. When the callback of the timer is queued, it has already timed out. Another reason is that the callback operation of the timer itself is too heavy and even has async operation, so that the running time cannot be estimated and the time can be set.

SetTimeout article

SetTimeout those things

As for the use of setTimeout to achieve the effect of repeated timing through its own iteration, the author first learned from the Little Red Book.

setTimeout(function(){ 
 var div = document.getElementById("myDiv"); 
 left = parseInt(div.style.left) + 5; 
 div.style.left = left + "px"; 
 if (left < 200){ 
    setTimeout(arguments.callee, 50); }}, 50);Copy the code

From JavaScript Advanced Programming (3rd edition), p. 611

This is pretty classic, but setTimeout itself takes extra time to run and then activate the next run. This will lead to a problem of continuous time delay. The original interval is 1000ms, but setTimeout may slowly run to a deviation of 2000ms in total under the unconscious delay.

Fixed limitations of setTimeout

When you think about trying to correct for time bias, what do you think? That’s right! This is the operation that gets the current time, and with this operation, we can fix the interval time each time we run so that the total time does not deviate too much.

/* id: specifies the timer id, which is customized. AminTime: specifies the execution interval. Callback: specifies the function to be executed at a specified time. Maximum duration for a timer to be reexecuted afterTimeUp: AfterTimeUp (id,usedTime,countTimes). AfterTimeUp (id,usedTime,countTimes). CountTimes is currently running timer callback number * / function runTimer (id, aminTime, callback, maxTime afterTimeUp) {/ /... Function getTime(){return new Date().getTime(); } /* diffTime: diffTime */ function timeout(diffTime){// main function, timer body //.... Let Runtime = amintime-diffTime // Calculate the next execution interval //.... timer=setTimeout(()=>{ //.... // Calculate the time to be deducted and execute the next call let TMP =startTime callback(id, Runtime,countTimes); startTime=getTime() diffTime=(startTime-tmp)-aminTime timeout(diffTime) },runtime) } //... }Copy the code

Start and end a repeat timer

Starting the repeat timer is simple, but stopping it is not. We can terminate the current repeat timer by creating a new setTimeout, such as the value of 20 seconds, after 20 seconds to end. There is no problem with this solution, but one more timer is added to the application, and one more timer is one more element of uncertainty.

Therefore, we can determine whether setTimeout is timed out by each time it is executed, and return if it is, without executing the next callback. Similarly, if you want to control by the number of executions you can do it this way.

function runTimer(id,aminTime,callback,maxTime,afterTimeUp){
    //...
    functionTimeout (diffTime){// Main function, timer body //....if(getTime() -usedtime >=maxTime){// timeout cleartimer cleartimer()return
        }
        timer=setTimeout(()=>{
            //
            if(getTime()-usedTime>=maxTime){return} / /.. },runtime) }function cleartimer(){// Clear timer //... }function starttimer() {/ /... Timeout (0)// Because there is no time difference in the initial execution, so 0}return{clearTimer, startTimer}// Return both methods for easy call}Copy the code

Stop by number of times, which we can determine in each callback.

let timer;
timer=runTimer("a", 100,function(id,runtime,counts){
    if(counts===2){// Stop timer.clearTimer ()}},1000,function(id,usedTime,counts){})
timer.starttimer()
Copy the code

Through the idea of stopping timer in accordance with the number of times above, then we can do a manual stop. Creates a parameter that monitors whether the timer needs to be stopped or, if true, stops the timer.

let timer;
let stop=false
setTimeout(()=>{
    stop=true
},200)
timer=runTimer("a",100,function(id,runtime,counts){
    if(stop){
       timer.cleartimer()
    }
},1000,function(id,usedTime,counts){})
timer.starttimer()
Copy the code

SetInterval article

SetInterval those things

You might think that setTimeout is more efficient than setInterval, but the truth is that actions speak louder than words, and setInterval does a little better. But make setInterval a high-performance repeat timer, because it has so many bugs that it doesn’t work. After the author’s transformation, Interval can be said to be equal to setTimeout.

Encapsulate setInterval into the same function as setTimeout, including its usage, except that setInterval does not need to call itself repeatedly. You just need to control the time in the callback function.

timer=setInterval(()=>{
    if(getTime()-usedTime>=maxTime){ 
        cleartimer()
        return
    }
    countTimes++
    callback(id,getTime()-startTime,countTimes);
    startTime=getTime();
},aminTime)
Copy the code

To prove Interval’s performance, here’s a rundown of the two.

Nodejs in:

In the browser:

How efficient is the timer when there is little pressure to render or calculate

Timer efficiency under high pressure of re-rendering or calculation

The first is performance without pressure, Interval wins!

And then a very stressful situation, right? . At the same time and under the same pressure, the setTimeout is not executed at all. The setInterval discarded other callbacks of the same timer in the same queue, that is, only the first callback in the queue was kept.

This means that in the case of synchronous operation, the performance of the two is not much different, you can use either. But in asynchronous cases, such as Ajax round-robin (websocket is out of the question), the only option we have is setTimeout for one reason only – who knows how long it will take for Ajax to return, and in this case only setTimeout will do the job.

SetTimeout is not better than setInterval, except that it is used in a wide range of scenarios. In terms of performance, setTimeout is no better than setInterval. So why? The next section diagnoses the cause in terms of event loops, memory leaks, and garbage collection.

EventLoop

To understand why neither of these callbacks can be executed accurately, we need to start with the properties of the event loop.

JS is single threaded

Before we get down to business, let’s discuss the features of JS. What makes it different from other programming languages? Although I haven’t had any in-depth exposure to other languages, one thing is for sure: JS is for browsers, and browsers can read JS directly.

Another common word for JS is single thread. So what is single threading? Do one thing at a time. For example, if you can’t do anything but read while you’re studying, that’s single-threaded. For example, some mothers are good at knitting and watching TV at the same time, which is multi-threading and can do two things at the same time.

JS is non-blocking

JS is not only a single-threaded language, but also a non-blocking language, which means that JS does not wait for an asynchronous load to complete, such as an interface read or a network resource such as a picture or video. Skip the asynchrony and execute the code below. Aren’t asynchronous functions never executed?

eventloop

So how should JS handle asynchronous callback methods? An eventLoop, then, goes through an infinite loop, looking for a function that meets the criteria and executing it. But JS is busy, and if there is a constant flow of tasks, JS will never get to the next loop. JS said I am very tired, I do not work, strike.

The stack and queue

A stack is a heap of JS work, constantly doing work and pushing tasks out of the stack. The queue is then the next round of tasks that need to be executed, and all unexecuted tasks are pushed into this queue. Wait for the current stack to clear, then loop the eventLoop to the queue and push the tasks in the queue onto the stack one by one.

Because the eventLoop loop is timed according to the stack. Just like buses, although the time between stops can be estimated, accidents are inevitable, such as traffic jam, such as too many passengers resulting in a long time to get on the bus, such as accidentally eating red lights at every intersection and other accidents, will lead to the delay of the bus. The stack of an Eventloop is a variable factor. It is possible that all tasks in the stack will be completed before the tasks in the queue are pushed, causing the execution time to be different each time.

Diagnose setTimeout and setInterval

Those years of setInterval back pot — prone to memory leaks

Garbage collection is a reference to memory leaks, and the two concepts are easy to explain together, but good gay friends. What is a memory leak? The concept of creating a variable or defining an object that is not used is not reclaimed by the system, so that the system has no new memory allocated to the variable that needs to be created later. Simply put, it means you’re in debt. So the garbage collection algorithm is there to help reclaim this memory, but there are some things that the app doesn’t need, but the developer doesn’t release them, so I don’t need them but I don’t want to let go, and garbage collection has no choice but to skip them and collect the garbage that has been discarded. So how do we tell the garbage collection algorithm, I don’t want this stuff, you can take it? What kind of spicy chicken can be recycled to make room for the new spicy chicken? Ultimately, it’s a matter of programming habits.

There is only one final cause of memory leak, which is that the garbage collection fails to reclaim the memory without releasing the parameters defined.

So how is memory allocated?

For example, if we define a constant var a=”apple”, memory will allocate space for the string apple. You might think, well, it’s just a string, how much memory can it take up. True, strings don’t take up much memory, but what about an array of thousands? That takes up a lot of memory, and if you don’t release it in time, the follow-up work will be very difficult.

But the concept of memory is so abstract, how do you feel about how much memory is being used or how much memory is being freed? Open Chrome’s Memory widget to show you how to feel Memory.

Here we create a demo to test how memory works:

letArray =[]// createArray createArray()//push content to add memoryfunction createArray() {for(letj=0; j<100000; j++){ array.push(j*3*5) } }function clearArray(){
    array=[]
}

let grow=document.getElementById("grow")
grow.addEventListener("click",clearArray)// Clear the array, that is,clear the memoryCopy the code

Practice is the only way to get the truth. Using Chrome’s testing tools, we can see that clearing the contents assigned to a variable frees memory, which is why many code ends with XXX =null, to free memory.

Now that we know how memory is freed, what happens when we can’t free memory even if we empty variables?

A set of experiments was done, array is a variable defined within a function, and a global variable

let array=[] createArray() function createArray(){ for(let j=0; j<100000; j++){ array.push(j*3*5) } }Copy the code
createArray() function createArray(){ let array=[] for(let j=0; j<100000; j++){ array.push(j*3*5) } }Copy the code

As a surprise, the internal memory is automatically freed after the function runs, without resetting, while the global variable remains. That is, the variable is hoisted and the memory cannot be freed if the reference is not cleared in time.

There is another case related to the DOM — creating and deleting the DOM. There is a classic set of cases where a free-floating DOM cannot be reclaimed. The following code, root has been removed, so can the child elements of root be reclaimed?

let root=document.getElementById("root")
for(leti=0; i<2000; i++){let div=document.createElement("div")
    root.appendChild(div)
}
document.body.removeChild(root)
Copy the code

The answer is no, because the root reference is still there, deleted from the DOM, but the reference is still there, at which point the child elements of root exist as free DOM and cannot be reclaimed. The solution is root=null, which clears references and removes the dom from the powerful state.

If there is something in the setInterval that cannot be reclaimed, that portion of memory can never be freed, resulting in a memory leak. So it’s a matter of programming habit, memory leak? SetInterval doesn’t take the blame.

Garbage collection mechanism

After discussing the causes of memory leaks, garbage collection mechanisms. There are two types: Reference-counting and Mark Sweap.

Reference-counting Indicates the reference count

This is easier to understand, is whether the current object is referenced, if referenced flag. The last ones that are not marked are cleared. The problem with this is that two unnecessary parameters in the program refer to each other, so that both are marked, and then neither can be deleted, which is locked. To solve this problem, mark Sweap emerged.

mark sweap

Mark Sweap. This method starts with the program’s global and marks parameters referenced by global. Finally, all unmarked objects are cleared. This can solve the problem that two objects reference each other and cannot be freed.

Since it is marked from global, variables in the scope of the function will be freed when the function completes.

Through garbage collection, we can also see that we need to be careful about what is defined in global, because global is the main function, and browsers don’t just clean it up. So pay attention to the variable lift problem.

conclusion

The failure to find a stone hammer suggests that setInterval is the cause of the memory leak. The memory leak is clearly due to bad coding habits, and setInterval is not responsible for this.