Know the anti-shake and throttling functions
? The concept of anti-shake and throttling actually did not first appear in software engineering. Anti-shake appeared in electronic components, and throttling appeared in fluid flow< /strong>medium
- JavaScript is event-driven, and a large number of operations will trigger events and add them to the event queue for processing.
- For some frequent event processing that will cause performance loss, we can limit the frequent occurrence of events through anti-shake and throttling;
? Anti-shake and throttling functions are already two very important functions in the actual front-end development, and they are also interview questions that are often asked in interviews.
? But many front-end developers are a little confused when faced with these two functions:
- Some developers can’t tell the difference between anti-shake and throttling at all (interviews are often asked);
- Some developers can tell the difference, but don’t know how to apply it;
- Some developers will use some third-party libraries, but they don’t know the internal principles, let alone write;
? Next, we will learn the anti-shake and throttling functions together:
- We not only need to distinguish the difference between anti-shake and throttling, but also understand which scenes will be used in actual work;
- And I will take you a little bit to write your own anti-shake and throttling function, not only understand the principle, but also learn to write it yourself;
Know the anti-shake debounce function
Let’s use a picture to understand its process:
When an event is triggered, the corresponding function will not be triggered immediately, but will wait for a certain period of time;
When events are triggered intensively, the triggering of functions will be frequently delayed;
Only after waiting for a period of time and no event is triggered, will the response function be actually executed;
There are many application scenarios for anti-shake:
? Frequently enter content in the input box, search or submit information;
? Click the button frequently to trigger an event;
? Listen to browser scrolling events to complete certain specific operations;
? The resize event when the user zooms the browser;
A case of anti-shake function
We have all encountered such a scenario, Input what you want to search in a search box:
For example, if you want to search for a MacBook:
- When I input m, for a better user experience, the corresponding association content usually appears, and these association contents are usually saved on the server, so a network request is required;
- When continuing to enter ma, send the network request again;
- Then the macbook needs to send a total of 7 network requests;
- This greatly depletes the performance of our entire system, whether it is the front-end event processing or the pressure on the server;
? But do we need so many network requests?
? No, the correct way is to send network requests under appropriate circumstances;
- For example, if the user quickly enters a macbook, only a network request is sent;
- For example, if the user enters an m and thinks for a while, m should indeed send a network request at this time;
- That is, we should monitor the user for a certain time, such as within 500ms, and send the network request when there is no trigger time again;
This is the anti-shake operation:_only when a function is not triggered again within a certain period of time, this function is actually called;
Know the throttle function
We use a picture to understand the throttling process:
When the event is triggered, the response function of this event will be executed;
If this event will be triggered frequently, the throttling function will execute the function according to a certain frequency;
No matter how many times this event is triggered in the middle, the frequency of executing the function is always fixed;
Throttling application scenarios:
? Listen to the scrolling event of the page;
? Mouse move event;
? The user frequently clicks the button action;
? Some design in the game;
Application scenario of throttling function
? Many people have played games similar to Airplane Wars
? In the game of Airplane Wars, we press the space to fire a bullet:
- There is such a setting in many aircraft war games, even if the pressing frequency is very fast, the bullets will be fired at a certain frequency; ?
- For example, it can only be fired once a second, even if the user presses 10 times in this second, the bullet will keep firing at the frequency of one bullet;
- But the event is triggered 10 times, and the corresponding function is only triggered once;
Example in life: anti-shake and throttling
? Examples of anti-shake in daily life:
? For example, one day when I finish class, I say that everyone has any questions to ask me, and I will wait for five minutes.
? If no students ask me a question within five minutes, then I will dismiss the get out of class;
- During this period, classmate a came to ask questions and helped him answer them. After answering, I would wait for five minutes again to see if other students asked questions;
- If I wait for more than 5 minutes, I click on the end of get out of class (this time is actually executed);
? Examples of throttling in life:
? For example, one day after I finished class, I said that everyone has any questions to ask me, but within 5 minutes, no matter how many students come to ask questions, I will only answer one question;
? After answering a question, if there is no student asking a question after 5 minutes, then the get out of class will be dismissed;
Introduction to the Underscore library
? In fact, we can achieve anti-shake operation through some third-party libraries:
lodash
underscore
? Using underscore here, we can understand that lodash is an upgraded version of underscore, which is more heavyweight and has more functions; but at present, I see that underscore is still being maintained, and lodash has not been updated for a long time;
? Underscore’s official website: https://underscorejs.org/
? There are many ways to install Underscore: Download Underscore and import it locally; import it directly through CDN; manage the installation through the package management tool (npm);
? Here we go directly through the CDN:
Custom anti-shake and throttling functions
Anti-Shake
<input type="text"> <button>Send</button> <button class="cancel">Cancel</button>
We implement it in the following way:
Basic anti-shake function realization: anti-shake effect can be achieved
function hydebounce(fn, delay) {<!-- --> // 1. The timer used to record the last event trigger let timer = null // 2. The function executed when the event is triggered const _debounce = () => {<!-- --> // 2.1. If there is a re-trigger (more trigger) event, then cancel the last event if (timer) clearTimeout(timer) // 2.2. Delay to execute the corresponding fn function (incoming callback function) timer = setTimeout(() => {<!-- --> fn() timer = null // After executing the function, reset the timer to null }, delay); } // return a new function return_debounce } // 1. Get the input element const inputEl = document. querySelector("input") // 3. Self-implemented anti-shake let counter = 1 inputEl.oninput = hydebounce(function() {<!-- --> console.log(`Send network request ${<!-- -->counter + + }`) }, 1000)
Optimization 1: Optimizing parameters and this point
function hydebounce(fn, delay) {<!-- --> // 1. The timer used to record the last event trigger let timer = null // 2. The function executed when the event is triggered const _debounce = function(...args) {<!-- --> // 2.1. If there is a re-trigger (more trigger) event, then cancel the last event if (timer) clearTimeout(timer) // 2.2. Delay to execute the corresponding fn function (incoming callback function) timer = setTimeout(() => {<!-- --> fn. apply(this, args) timer = null // After executing the function, reset the timer to null }, delay); } // return a new function return_debounce } let counter = 1 inputEl.oninput = hydebounce(function(event) {<!-- --> console.log(`Send network request ${<!-- -->counter + + }:`, this, event) }, 1000)
Optimization 2: Optimize cancel operation (add cancel function)
function hydebounce(fn, delay) {<!-- --> // 1. The timer used to record the last event trigger let timer = null // 2. The function executed when the event is triggered const _debounce = function(...args) {<!-- --> // 2.1. If there is a re-trigger (more trigger) event, then cancel the last event if (timer) clearTimeout(timer) // 2.2. Delay to execute the corresponding fn function (incoming callback function) timer = setTimeout(() => {<!-- --> fn. apply(this, args) timer = null // After executing the function, reset the timer to null }, delay); } // 3. Bind a cancel function to _debounce _debounce. cancel = function() {<!-- --> if (timer) clearTimeout(timer) } // return a new function return_debounce } // -------- let counter = 1 const debounceFn = hydebounce(function(event) {<!-- --> console.log(`Send network request ${<!-- -->counter + + }:`, this, event) }, 5000) inputEl.oninput = debounceFn // 4. Implement the cancel function cancelBtn.onclick = function() {<!-- --> debounceFn. cancel() }
Optimization 3: Optimize the effect of immediate execution (immediate execution for the first time)
function hydebounce(fn, delay, immediate = false) {<!-- --> // 1. The timer used to record the last event trigger let timer = null let isInvoke = false // 2. The function executed when the event is triggered const _debounce = function(...args) {<!-- --> // 2.1. If there is a re-trigger (more trigger) event, then cancel the last event if (timer) clearTimeout(timer) // The first operation does not need to be delayed if (immediate & amp; & amp; !isInvoke) {<!-- --> fn. apply(this, args) isInvoke = true return } // 2.2. Delay to execute the corresponding fn function (incoming callback function) timer = setTimeout(() => {<!-- --> fn. apply(this, args) timer = null // After executing the function, reset the timer to null isInvoke = false }, delay); } // 3. Bind a cancel function to _debounce _debounce. cancel = function() {<!-- --> if (timer) clearTimeout(timer) timer = null isInvoke = false } // return a new function return_debounce } let counter = 1 const debounceFn = hydebounce(function(event) {<!-- --> console.log(`Send network request ${<!-- -->counter + + }:`, this, event) }, 100) inputEl.oninput = debounceFn // 4. Implement the cancel function cancelBtn.onclick = function() {<!-- --> debounceFn. cancel() }
Optimization 4: Optimize the return value
// Principle: A function does one thing, and a variable is also used to record a state function hydebounce(fn, delay, immediate = false, resultCallback) {<!-- --> // 1. The timer used to record the last event trigger let timer = null let isInvoke = false // 2. The function executed when the event is triggered const _debounce = function(...args) {<!-- --> return new Promise((resolve, reject) => {<!-- --> try {<!-- --> // 2.1. If there is a re-trigger (more trigger) event, then cancel the last event if (timer) clearTimeout(timer) // The first operation does not need to be delayed let res = undefined if (immediate & amp; & amp; !isInvoke) {<!-- --> res = fn. apply(this, args) if (resultCallback) resultCallback(res) resolve(res) isInvoke = true return } // 2.2. Delay to execute the corresponding fn function (incoming callback function) timer = setTimeout(() => {<!-- --> res = fn. apply(this, args) if (resultCallback) resultCallback(res) resolve(res) timer = null // After executing the function, reset the timer to null isInvoke = false }, delay); } catch (error) {<!-- --> reject(error) } }) } // 3. Bind a cancel function to _debounce _debounce. cancel = function() {<!-- --> if (timer) clearTimeout(timer) timer = null isInvoke = false } // return a new function return_debounce } // 1. Get the input element const inputEl = document. querySelector("input") const cancelBtn = document. querySelector(".cancel") // 2. Manually bind function and execute const myDebounceFn = hydebounce(function(name, age, height) {<!-- --> console.log("----------", name, age, height) return "coderwhy hahahaha" }, 1000, false) myDebounceFn("why", 18, 1.88).then(res => {<!-- --> console.log("Get the execution result:", res) })
Throttling
We implement it in the following way:
Basic implementation of throttling function: throttling effect can be achieved
<input type="text"> <button>Send</button>
function mythrottle(fn, interval) {<!-- --> let startTime = 0; const _throttle = function (...args) {<!-- --> const nowTime = new Date(). getTime(); const waitTime = interval - (nowTime - startTime); if (waitTime <= 0) {<!-- --> fn.apply(this, args); startTime = nowTime; } } return_throttle } const inputCxt = document. querySelector("input"); const btnSend = document. querySelector("button"); let count = 1; inputCxt.oninput = mythrottle(function (event) {<!-- --> console.log(`The request for ${<!-- -->count + + }:`, this.value, event) }, 3000);
Optimization 1: Throttling can also be executed for the last time
The throttling function is executed immediately by default
function mythrottle(fn, interval, leading = true) {<!-- --> let startTime = 0; const _throttle = function (...args) {<!-- --> const nowTime = new Date(). getTime(); // control executes immediately if (!leading & amp; & amp; startTime === 0) {<!-- --> startTime = nowTime } const waitTime = interval - (nowTime - startTime); if (waitTime <= 0) {<!-- --> fn.apply(this, args); startTime = nowTime; } } return_throttle }
function mythrottle(fn, interval, {<!-- --> leading = true, trailing = false } = {<!-- -->}) {<!-- --> let startTime = 0; let timer = null; const _throttle = function (...args) {<!-- --> const nowTime = new Date(). getTime(); // control executes immediately if (!leading & amp; & amp; startTime === 0) {<!-- --> startTime = nowTime } const waitTime = interval - (nowTime - startTime); if (waitTime <= 0) {<!-- --> if (timer) clearTimeout(timer); fn.apply(this, args); startTime = nowTime; timer = null; return } // Determine whether to execute the tail if (trailing & amp; & amp; !timer) {<!-- --> // Execution time for the tail timer = setTimeout(() => {<!-- --> fn.apply(this, args); startTime = new Date().getTime(); timer = null; }, waitTime); } } return_throttle }
Optimization 2: Optimize and add cancel function
<input type="text"> <button>Send</button> <button class="cancel">Cancel</button>
function mythrottle(fn, interval, {<!-- --> leading = true, trailing = false } = {<!-- -->}) {<!-- --> let startTime = 0; let timer = null; const _throttle = function (...args) {<!-- --> const nowTime = new Date(). getTime(); // control executes immediately if (!leading & amp; & amp; startTime === 0) {<!-- --> startTime = nowTime } const waitTime = interval - (nowTime - startTime); if (waitTime <= 0) {<!-- --> if (timer) clearTimeout(timer); fn.apply(this, args); startTime = nowTime; timer = null; return } // Determine whether to execute the tail if (trailing & amp; & amp; !timer) {<!-- --> // Execution time for the tail timer = setTimeout(() => {<!-- --> fn.apply(this, args); startTime = new Date().getTime(); timer = null; }, waitTime); } } // Cancel _throttle. cancel = function () {<!-- --> if (timer) clearTimeout(timer); startTime = 0; timer = null; } return_throttle }
const inputCxt = document. querySelector("input"); const btnSend = document. querySelector("button"); const cancelBtn = document.querySelector(".cancel"); let count = 1; const throttle = mythrottle(function (event) {<!-- --> console.log(`The request for ${<!-- -->count + + }:`, this.value, event) }, 1000, {<!-- --> trailing: true }); inputCxt.oninput = throttle; cancelBtn.onclick = function () {<!-- --> throttle. cancel(); }
Optimization 3: Optimize the return value problem
function mythrottle(fn, interval, {<!-- --> leading = true, trailing = false } = {<!-- -->}) {<!-- --> let startTime = 0; let timer = null; const _throttle = function (...args) {<!-- --> return new Promise((resolve, reject) => {<!-- --> try {<!-- --> const nowTime = new Date(). getTime(); // control executes immediately if (!leading & amp; & amp; startTime === 0) {<!-- --> startTime = nowTime } const waitTime = interval - (nowTime - startTime); if (waitTime <= 0) {<!-- --> if (timer) clearTimeout(timer); const res = fn. apply(this, args); resolve(res); startTime = nowTime; timer = null; return } // Determine whether to execute the tail if (trailing & amp; & amp; !timer) {<!-- --> // Execution time for the tail timer = setTimeout(() => {<!-- --> const res = fn. apply(this, args); resolve(res); startTime = new Date().getTime(); timer = null; }, waitTime); } } catch (error) {<!-- --> reject(error) } }) } // Cancel _throttle. cancel = function () {<!-- --> if (timer) clearTimeout(timer); startTime = 0; timer = null; } return_throttle }
const throttle = mythrottle(function (event) {<!-- --> console.log(`The request for ${<!-- -->count + + }:`, this.value, event) return "throttle return value" }, 1000, {<!-- --> trailing: true }); throttle("aaaaa").then((res) => {<!-- --> console.log("res", res) })
Custom deep copy function
Earlier we have learned some relationships between objects assigning values to each other, including:
Imported assignment: point to the same object and affect each other;
Shallow copy of objects: it is only a shallow copy, and when objects are introduced internally, they will still affect each other;
Deep copy of the object: the two objects no longer have any relationship and will not affect each other;
We can already implement deep copy through a method:
JSON.parse This deep copy method is actually unable to handle functions, Symbols, etc.;
And if there is a circular reference to the object, an error will also be reported;
const info = {<!-- --> name: "why", age: 18, friend: {<!-- --> name: "kobe" }, running: function() {<!-- -->}, [Symbol()]: "abc", // obj: info } info.obj = info // 1. Operation 1: Reference assignment // const obj1 = info // 2. Operation 2: shallow copy // const obj2 = { ...info } // // obj2.name = "james" // // obj2.friend.name = "james" // // console.log(info.friend.name) // const obj3 = Object. assign({}, info) // // obj3.name = "curry" // obj3.friend.name = "curry" // console.log(info.friend.name) // 3. Operation three: deep copy // 3.1. JSON method // const obj4 = JSON. parse(JSON. stringify(info)) // info.friend.name = "curry" // console.log(obj4.friend.name) // console. log(obj4)
Custom deep copy function:
1. Customize the basic functions of deep copy;
2. Process the key of Symbol;
3. Value process processing of other data types: array, function, Symbol, Set, Map;
4. Handling of circular references;
// Determine the data type function isObject(value) {<!-- --> const valueType = typeof value; return (value !== null) & amp; & amp; (valueType === "function" || valueType === "object") }
// deep copy function deepCopy(originValue, map = new WeakMap()) {<!-- --> // If the value is of Symbol type if (typeof originValue === "symbol") {<!-- --> return Symbol(originValue.description) } // If it is a primitive type, return directly if (!isObject(originValue)) {<!-- --> return originValue } // If there is a Set type if (originValue instanceof Set) {<!-- --> const newSet = new Set(); for (const setItem of originValue) {<!-- --> newSet.add(deepCopy(setItem)) } return newSet } // If it is a function type, no deep copy is required if (typeof originValue === "function") {<!-- --> return originValue } // If it is an object type, you need to create an object if (map.get(originValue)) {<!-- --> return map.get(originValue); } // create array const newObj = Array.isArray(originValue) ? [] : {<!-- -->}; map.set(originValue, newObj); // Traversing common keys for (const key in originValue) {<!-- --> newObj[key] = deepCopy(originValue[key], map); } // traverse symbol alone const keySymbols = Object. getOwnPropertySymbols(originValue); for (const symbolKey of keySymbols) {<!-- --> newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map) } return newObj }
const set = new Set(["abc", "cba", "nba"]) const s1 = Symbol("s1") const s2 = Symbol("s2") const info = {<!-- --> name: "why", age: 18, friend: {<!-- --> name: "kobe", address: {<!-- --> name: "Los Angeles", detail: "Stamps Center" } }, // 1. Special type: Set set, // 2. Feature type: function running: function () {<!-- --> console.log("running~") }, // 3. Special type of value: Symbol symbolKey: Symbol("abc"), // 4. When the key is a symbol [s1]: "aaaa", [s2]: "bbbb", // self: info } info.self = info; const newObj = deepCopy(info); console. log(newObj); console.log(newObj === newObj.self);//true