Anti-shake and throttling of js function

file

This article was first published at: https://github.com/bigo-frontend/blog/ Welcome to follow and repost.

Anti-shake and throttling of js function

Anti-shake throttling

concept

  • debounce : Merge events that trigger very frequently (such as keystrokes) into one execution.

  • throttle : Guarantee a constant number of executions every X milliseconds, such as checking the scroll position every 200ms, and triggering CSS animations.

  • requestAnimationFrame : It can replace throttle. When the function needs to recalculate and render the elements on the screen, it can be used to ensure the smoothness of animation or changes. Note: IE9 is not supported.

The significant difference between anti-shake and throttling is that throttling will only execute a task within a specified time interval, while anti-shake means that the interval between tasks exceeds a certain threshold before executing a task.

debounce

The anti-shake function debouncerefers to a function within a certain period of time, no matter how many callbacks are triggered, it will only execute the last time , that is, events that trigger frequent events are combined into one execution (such as keyboard input).

The realization principle is to use the timer, set a timer when the function is executed for the first time, and then clear the previous timer when it is found that the timer has been set when it is called, and reset a new timer. The cleared timer triggers the execution of the function when the timer expires.

cosnt debounce = function(func, wait, immediate) {
    
    
  let timer = null;

  // 定时器计时结束后
  // 1、清空计时器,使之不影响下次连续事件的触发
  // 2、触发执行 func
  let later = function(context, args) {
    
    
    timer = null;
    return func.apply(context, args);
  }
  let handler = function(...args) {
    
    
    const context = this;
  	if (timer) {
    
    
    	clearTimeout(timer);
    }
    let result;

    // immediate为true则首次立马执行函数,不需要等wait时间
    // 根据 timer 是否为空可以判断是否是首次触发
		if (immediate) {
    
    
			let shouldCall = !timer;
			timer = setTimeout(later.bind(null, context, args), wait);
			if (shouldCall) {
    
    
      	result = func.apply(context, args);
      }
    } else {
    
    
    	timer = setTimeout(later.bind(null, context, args), wait);
    }
    return;
  }

  return handler;
}

// DEMO1
// 执行 debounce 函数返回新函数
const betterFn = debounce(() => console.log('fn 防抖执行了'), 1000)
// 停止滑动 1 秒后执行函数 () => console.log('fn 防抖执行了')
document.addEventListener('scroll', betterFn)

// DEMO2
// 执行 debounce 函数返回新函数
const betterFn = debounce(() => console.log('fn 防抖执行了'), 1000, true)
// 立即执行函数 () => console.log('fn 防抖执行了')
// 停止滑动 1 秒后再次执行函数 () => console.log('fn 防抖执行了')
document.addEventListener('scroll', betterFn)

throttle

Guarantee a constant number of executions every X milliseconds, such as handling page scrolling every 200ms

There are two implementation options:

  • The first is to use the timestamp to determine whether the execution time has come, record the timestamp of the last execution, and then execute the callback every time an event is triggered, and judge in the callback whether the interval between the current timestamp and the last execution timestamp has reached the time difference ( Xms), if it is, execute it, and update the timestamp of the last execution, and so on.
function throttle(func, threshold = 200) {
    
    
  // 上一次执行fn的时间
	let previous = 0;
  return function(...args) {
    
    
    // 获取当前时间
    let now = +new Date();
    // 将当前时间和上一次执行函数的时间进行对比
    // 大于等待时间就把 previous 设置为当前时间并执行函数 fn
    if (now - previous > threshold) {
    
    
      previous = now;
      func.apply(this, args);
    }
  }
}
  • The second method is to use a timer. For example, when scrollthe event is just triggered, print a hello world , and then set a 1000mstimer. After that, every time scrollthe event is triggered, the callback will be triggered. If the timer already exists, the callback will not execute the method until the timer is set. timer triggers, handleris cleared, and then resets the timer.
function throttle(func, threshold = 200) {
  // 设置一个定时器
	let timer = null;
  return function(...args) {
  	// 如果定时器存在则返回
  	if (timer) return;
    timer = setTimeout(() => {
    	// 执行函数并将定时器置为null
    	timer = null;
    	func.apply(this, args);
    }, threshold);
  }
}

Let's look at the response time of the above two timers. The first timer is executed immediately , and the second timer needs to wait for the threshold before executing. Can we implement a timer that can be executed immediately or wait for the threshold? What about functions that are executed later, or functions that can be executed immediately and executed again at the end?

Combining the above two methods, we added leading and trailing parameters to judge

  • Configure whether to respond to the callback at the beginning of the event ( leadingparameter, falseignored)
  • Configure whether to respond to the callback after the end of the event ( trailingparameter, falseignored)
const throttle = function(func, wait, options) {
    
    
  var timeout, context, args, result;
  
  // 上一次执行回调的时间戳
  var previous = 0;
  
  // 无传入参数时,初始化 options 为空对象
  if (!options) options = {
    
    };

  var later = function() {
    
    
    // 当设置 { leading: false } 时
    // 每次触发回调函数后设置 previous 为 0
    // 不然为当前时间
    previous = options.leading === false ? 0 : _.now();
    
    // 防止内存泄漏,置为 null 便于后面根据 !timeout 设置新的 timeout
    timeout = null;
    
    // 执行函数
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  // 每次触发事件回调都执行这个函数
  // 函数内判断是否执行 func
  // func 才是我们业务层代码想要执行的函数
  var throttled = function() {
    
    
    
    // 记录当前时间
    var now = _.now();
    
    // 第一次执行时(此时 previous 为 0,之后为上一次时间戳)
    // 并且设置了 { leading: false }(表示第一次回调不执行)
    // 此时设置 previous 为当前值,表示刚执行过,本次就不执行了
    if (!previous && options.leading === false) previous = now;
    
    // 距离下次触发 func 还需要等待的时间
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    
    // 要么是到了间隔时间了,随即触发方法(remaining <= 0)
    // 要么是没有传入 {leading: false},且第一次触发回调,即立即触发
    // 此时 previous 为 0,wait - (now - previous) 也满足 <= 0
    // 之后便会把 previous 值迅速置为 now
    if (remaining <= 0 || remaining > wait) {
    
    
      if (timeout) {
    
    
        clearTimeout(timeout);
        
        // clearTimeout(timeout) 并不会把 timeout 设为 null
        // 手动设置,便于后续判断
        timeout = null;
      }
      
      // 设置 previous 为当前时间
      previous = now;
      
      // 执行 func 函数
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
    
    
      // 最后一次需要触发的情况
      // 如果已经存在一个定时器,则不会进入该 if 分支
      // 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支
      // 间隔 remaining milliseconds 后触发 later 方法
      timeout = setTimeout(later, remaining);
    }
    return result;
  };

  return throttled;
};

// DEMO
// 执行 throttle 函数返回新函数
const betterFn = throttle(() => console.log('fn 函数执行了'), 1000)
// 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn
setInterval(betterFn, 10)

requestAnimationFrame

It can replace throttle. When the function needs to recalculate and render the elements on the screen, it can be used to ensure the smoothness of animation or changes. Note: IE9 is not supported.

let start = null;
const element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

const step = function(timestamp) {
    
    
  if (!start) start = timestamp;
  let progress = timestamp - start;
  element.style.left = Math.min(progress / 10, 200) + 'px';
  if (progress < 2000) {
    
    
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);

Welcome everyone to leave a message to discuss, I wish you a smooth work and a happy life!

I am the front end of bigo, see you in the next issue.

Guess you like

Origin blog.csdn.net/yeyeye0525/article/details/120948350