Anti shake throttling and EventLoop you may not know

yun_ Little dream 2021-09-15 10:21:47

Anti shake and throttling

Shake proof

Anti shake function principle : When the event is triggered n Seconds before the callback , If in this n It's triggered in seconds , Then the time will be counted again . Pay attention : If n It's triggered in seconds , Then the time will be counted again . in other words , He is through “ Empty setTimeout And recalculate ” The way it works .

// Shake proof 
let timer=null;
$(window).scroll(()=>{
if(timer){
clearTimeout(timer);
}
timer=setTimeout(()=>{
// Time delay 200ms, Handle scrolling logic 
},200);
})
 Copy code 

This is a piece of code I wrote in the scrolling of the applet page , He made it perfect Stop and then execute Such a solution is due to the monitoring of functions that are seriously performance consuming . Applicable scenario :

  1. Button submit scenario : Prevent multiple submissions button , Perform only the last commit
  2. Server verification scenario : Form validation needs server cooperation , Only the last time a continuous input event is executed , And search association words have similar functions

Handwritten version of interview :

function debounce(fn, delay, args, context) {
let timer = null;
return function() {
context = context || this;
args = args || arguments;
if(timer != null) {
clearTimeout(timer);
}
timer = setTimeout(function() {
fn.apply(context, args);
}, delay)
}
}
 Copy code 

throttle

What is throttle ? Principle of throttling function : Within one unit time , Function can only be triggered once . If multiple functions are triggered in this unit time , Only ( The first ) One time . This is interesting : In a certain amount of time , If triggered , No longer execute the same code snippet !

// throttle 
let canRun=true;
window.addEventListener('resize',()=>{
if(!canRun){
return;
}
canRun=false;
setTimeout(()=>{
canRun=true;
// Something fun 
},300);
});
 Copy code 

This code is implemented in my website “ Monitor web page width changes to achieve some responsive functions ” The function of .( Of course , Here is a simplified version of . The concrete implementation is too long )

Handwritten version of interview

 function throttle(fn, delay, args, context) {
let timer = null;
return function() {
context = context || this;
args = args || arguments;
let flag = !timer;
if(!timer) {
timer = setTimeout(function() {
timer = null;
}, delay)
}
if(flag) {
fn.apply(context, args);
}
}
}
 Copy code 

This involves setTimeout The implementation mechanism of ——JS Single thread mechanism and asynchronous programming !

That's what they say setTimeout Whether to finish on time ? Actually ,js One in Event cycle mechanism , Because the main thread always has tasks , until setTimeout(fn,n) Of n Milliseconds later , The main thread will give up the task ( Or finish ), Then immediately execute macrotask Medium setTimeout Callback task .( Here's the picture )  Insert picture description here

js One more Queuing mechanism : Like you do setTimeout(task,100), It's just to make sure that the task , Will be in 100ms Back entry macrotask queue , But that doesn't mean he can do it right away , It is possible that a time-consuming operation is in progress on the main thread , Or at the moment macrotask There are many tasks in the queue , So use setTimeout As a countdown, it doesn't guarantee accuracy .

Time interval specified by timer , Indicates when to add the timer code to the message queue , Not when to execute the code . So there's no guarantee when the code will actually execute , Depends on when it is picked up by the main thread's event loop 、 And implement .

and , There is one setTimeout, Will enter a queue , Come in again , Will continue to “ line up ”, So in the code above , In the last setTimeout Of 300ms Inside , No more changes will be implemented setTimeout What's inside . That's why “canRun by false”->“ It only takes effect once in a certain period of time ” The effect of .

Applicable scenario :

  1. Drag scene : Only once in a fixed time , Prevent UHF trigger position change
  2. Zoom scene : Monitor browser resize
  3. Animated scene : Avoid triggering animation multiple times in a short time to cause performance problems

Event cycle mechanism : Main thread runtime , Generate heaps and stacks , The code in the stack calls all kinds of external API, After the asynchronous operation is completed , Just in the message queue . As long as the code in the stack is executed , The main thread will read the message queue , Execute the callback functions corresponding to those asynchronous tasks in turn . namely , The main thread repeatedly gets messages 、 Star News 、 Message again 、 Re execution —— So called “ The event loop ”!

The difference between anti shake and throttling ( Icon ):

 Insert picture description here

EventLoop( The event loop ) Detailed explanation

JS Execution is single threaded , It's based on “ The event loop ”.JS The process of execution is like this :

  1. All synchronization tasks are performed on the main thread , Form an execution stack ;
  2. Outside the main thread , There is also a “ Task queue ”. As long as asynchronous tasks have run results , It's just “ Task queue ” Put an event in ;
  3. once “ Execution stack ” All synchronization tasks in are completed , The system will read “ Task queue ”, Look at the events in there . The corresponding asynchronous tasks then end the wait state , Enter the execution stack , Start execution ;
  4. The main thread repeats the third step .!

And those marked with blue exclamation marks , That's what we're talking about “ The event loop ”!, also “ Macro task ” and “ Micro task ” Two concepts . Let's elaborate on :

  1. ( Synchronization code execution completed ) Do the micro task first : Check the micro task queue , Execute and empty the micro task queue , If a new micro task is added in this step , Will also be executed together ;
  2. Take a macro task from the task queue and execute
  3. Enter the update rendering phase , Determine whether rendering is required ( Note here : Not every round eventloop Will correspond to a browser rendering , According to the screen refresh rate 、 Page performance 、 Whether the page is running in the background, etc , But usually this rendering interval is fixed ( So there are multiple task It may be when a rendering is finished ))
  4. For documents that need to be rendered , Perform listening resizescroll、 Frame animation callback requestAnimationFrameIntersectionObserver、 Redraw the user interface
  5. Judge whether the current macro task and micro task queue are empty , if , Is to Idle Idle cycle algorithm , Decide whether to execute requestIdleCallback Callback
  6. Cycle these steps back and forth

What is a macro task ?

Macro task , be called (macro )task. Its purpose is to enable the browser to obtain JavaScript/Dom And ensure that the execution stack can proceed in sequence . Macro task scheduling can be seen everywhere , For example, parsing HTML、 Get the event callback of mouse click, etc .

Macrotask Common tasks :

  1. setTimeout
  2. setInterval
  3. setImmediate
  4. I/O
  5. User interaction ,UI Rendering

What is micro task ?

Micro task , be called micro task. Usually used for things that happen directly after the currently executing script , For example, respond to a series of behaviors 、 Or add some asynchronous tasks , Instead of creating a new macro task queue . As long as the execution stack has no other JavaScript In execution , At the end of each macro task , The micro task queue will be processed after the callback

Microtask Common tasks :

  1. Promise(=> then、async、await)
  2. MutationObserver
  3. process.nextTick(node.js)

Here's a little bit of attention :“ Task queue ” Is aimed at “ Asynchronous task ” Speaking of , stay promise in ,new Promise() It's a synchronization task ,then It's the asynchronous task !

Practice leads to : Micro tasks have “ High priority ” characteristic —— This is to ensure that the micro tasks in the queue are executed before an event loop . So in “ At the same level ” Next , Micro tasks execute before macro tasks ! But most articles say that the first step should be “ First take out the first macro task in the queue and execute ”. It makes me wonder ? If you understand, please teach me in the comments !

// 3. After emptying the micro task queue, start executing the first macro task 
setTimeout(()=>{
console.log('1');
// 4. New macro task , Put it aside 
setTimeout(()=>{
console.log('1-1')
})
// 5. Continue to empty the micro task queue , After this step, execute in sequence 2、4
Promise.resolve().then(()=>{
console.log('1-2')
})
})
// 1. Do the micro task first 
Promise.resolve().then(()=>{
console.log(2)
// 2. Generate a new macro task , Put it aside 
setTimeout(()=>{
console.log(3)
})
})
 Copy code 

test2


eventloop practice :$nextTick

vue This is how the official website describes :

Vue Updating DOM Time is executed asynchronously . Just listen for data changes ,Vue A queue will be opened , And buffer all data changes that occur in the same event cycle . If the same watcher Triggered multiple times , Will only be pushed into the queue once . This de duplication of data in buffering avoids unnecessary computation and DOM Operation is very important . then , In the next event loop “tick” in ,Vue Refresh the queue and execute the actual ( It's gone heavy ) Work .

// Source code 
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
const callbacks = []
let pending = false
/** * For all callback Traversal , Then point to the callback function of the response * Use callbacks Guaranteed to be in the same one tick Internal execution multiple times nextTick, Multiple asynchronous tasks will not be opened , And these asynchronous tasks are all pressed into a synchronous task , The next tick completion of enforcement . */
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]( "i")
}
}
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
// This will trigger the callback 
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// The function is to delay cb Executes after the execution of the current call stack 
export function nextTick (cb?: Function, ctx?: Object) {
// The callback function passed in will be in callbacks Save it 
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pending It's a status tag , Guarantee timerFunc The next tick Only once before 
if (!pending) {
pending = true
timerFunc()
}
// When nextTick When you don't transfer parameters , Provide a Promise The call of 
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
 Copy code 

among timerFunc() It's more important : It is based on the current environment to determine which way to implement , according to Promise.then and MutationObserver as well as setImmediate To determine the priority of , You can use whatever you support , If the execution environment does not support , Will be downgraded to setTimeout 0, Even though it has execution delays , May cause multiple renderings , There is no way .

Simply speaking :

  • vue Using asynchronous queues to control DOM Update and nextTick Callbacks are executed one after another
  • microtask Because of its high priority feature , It can ensure that the micro tasks in the queue are completed and prioritized before an event cycle
  • Because of compatibility issues ,vue Had to do microtask towards macrotask The downgrade of
Please bring the original link to reprint ,thank
Similar articles

2021-09-15

2021-09-15

2021-09-15

2021-09-15