First, let's understand what Throttle is
1. Definition
If you tighten the faucet until the water is flowing out in the form of water droplets, you will find that every once in a while, a drop of water will flow out.
That is to say, an execution cycle is set in advance, and when the time when the action is called is greater than or equal to the execution cycle, the action is executed, and then the next new cycle is entered.
Interface definition:
* When the frequency control returns the function is called continuously, the action execution frequency is limited to times/ delay* @param delay {number} Delay time, unit milliseconds* @param action {function} Requests the associated function, and the function that needs to be called in actual application* @return {function} Returns the client calling function*/throttle(delay,action)2. Simple implementation
var throttle = function(delay, action){ var last = 0return function(){ var curr = +new Date() if (curr - last > delay){ action.apply(this, arguments) last = curr } }}Let me explain this throttling function carefully below.
In the browser DOM event, some events will be triggered continuously with the user's operations. For example: resize the browser window, scroll the browser page, and mousemove. That is to say, when the user triggers these browser operations, if the corresponding event handling method is bound to the script, this method will be triggered continuously.
This is not what we want, because sometimes if the event handling method is relatively large, DOM operations such as complex, and constantly triggering such events will cause performance losses, resulting in a decline in user experience (slow UI response, browser stuck, etc.). So usually we will add logic to the corresponding event to delay execution.
Generally speaking, we use the following code to implement this function:
var COUNT = 0;function testFn() { console.log(COUNT++); }// When the browser resizes // 1. Clear the previous timer // 2. Add a timer to delay the real function testFn by 100 milliseconds to trigger window.onresize = function () { var timer = null; clearTimeout(timer); timer = setTimeout(function() { testFn(); }, 100);};Careful students will find that the above code is actually wrong. This is a problem that novices will make: the return value of the setTimeout function should be saved in a relative global variable, otherwise a new timer will be generated every time the resize is resized, which will not achieve the effect we send.
So we modified the code:
var timer = null;window.onresize = function () { clearTimeout(timer); timer = setTimeout(function() { testFn(); }, 100);};At this time, the code is normal, but there is another new problem - a global variable timer is generated. This is something we don't want to see. If this page has other functions, it is also called timer. Different codes will cause conflicts before. To solve this problem, we need to use a language feature of JavaScript: closures closures. Readers can learn about related knowledge in MDN. The modified code is as follows:
/** * Function throttling method* @param Function fn Delay calling function* @param Number delay How long is the delay* @return Function Method for delaying execution*/var throttle = function (fn, delay) { var timer = null; return function () { clearTimeout(timer); timer = setTimeout(function() { fn(); }, delay); }};window.onresize = throttle(testFn, 200, 1000);We use a closure function (throttle throttling) to put the timer internally and return the delay processing function. This way, the timer variable is invisible to the outside, but the timer variable can also be accessed when the internal delay function is triggered.
Of course, this writing method is not easy for novices to understand. We can change the writing method to understand:
var throttle = function (fn, delay) { var timer = null; return function () { clearTimeout(timer); timer = setTimeout(function() { fn(); }, delay); }}; var f = throttle(testFn, 200);window.onresize = function () { f();};Here is a point of view: the function returned by the throttle after being called is the real function that needs to be called when the onresize is triggered
Now it seems that this method is close to perfection, but it is not the case in actual use. For example:
If the user constantly resizes the browser window size, the delay processing function will not be executed once
So we need to add another function: when the user triggers resize, it should be triggered at least once within a certain period of time. Since it is within a certain period of time, this judgment condition can take the current time milliseconds, and each function call subtracts the current time from the last call time, and then judges that if the difference is greater than a certain period of time, it will be directly sent, otherwise it will still follow the timeout delay logic.
What needs to be pointed out in the following code is:
The function of previous variable is similar to that of timer. Both are the identifiers that record the last time and must be relative global variables
If the logic process follows the logic "triggered at least once", then the function call needs to be completed to reset the previous to the current time. In short, it is actually the current one compared to the next time.
/** * Function throttling method* @param Function fn Delay call function* @param Number delay How long is the delay* @param Number at least how long is it triggered* @return Function Method for delaying execution*/var throttle = function (fn, delay, at least) { var timer = null; var previous = null; return function () { var now = +new Date(); if ( !previous ) previous = now; if ( now - previous > at least ) { fn(); // Reset the last start time to the end time of this time previous = now; } else { clearTimeout(timer); timer = setTimeout(function() { fn(); }, delay); } }};practice:
We simulate a scene of throttling when a window scrolls, that is, when the user scrolls the page downward, we need to throttling some methods, such as: calculating the DOM position, etc., which requires continuous operation of DOM elements.
The complete code is as follows:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>throttle</title></head><body> <div style="height:5000px"> <div id="demo" style="position:fixed;"></div> </div> <script> var COUNT = 0, demo = document.getElementById('demo'); function testFn() {demo.innerHTML += 'testFN was called' + ++COUNT + 'time<br>';} var throttle = function (fn, delay, at least) { var timer = null; var previous = null; return function () { var now = +new Date(); if ( !previous ) previous = now; if ( at least && now - previous > at least ) { fn(); // Reset the last start time to the end time of this time previous = now; clearTimeout(timer); } else { clearTimeout(timer); timer = setTimeout(function() { fn(); previous = null; }, delay); } } } }; window.onscroll = throttle(testFn, 200); // window.onscroll = throttle(testFn, 500, 1000); </script></body></html>We use two cases to test the effect, namely adding at least triggering at least the at least and not adding:
// case 1window.onscroll = throttle(testFn, 200);// case 2window.onscroll = throttle(testFn, 200, 500);
case 1 manifests as: testFN will not be called during the page scrolling process (cannot be stopped), and will be called once until it stops, which means that the last setTimeout in the throttle is executed, the effect is as shown in the figure:
Case 2 is manifested as: during the page scrolling process (cannot be stopped), testFN will be delayed by 500ms for the first time (from at least delay logic), and then execute at least every 500ms. The effect is as shown in the figure
As shown above, the effects we want to achieve have been introduced and examples are provided. I hope it will be helpful to friends in need. Readers can think about some subsequent auxiliary optimizations by themselves, such as: function this pointing, return value saving, etc. Anyway, it feels great to understand this process carefully!