[JavaScript] anti-shake, throttling, deep copy

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
syntaxbug.com © 2021 All Rights Reserved.