Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 3 题:什么是防抖和节流?有什么区别?如何实现? #5

Open
KouYidong opened this issue Jan 23, 2019 · 76 comments
Open
Labels

Comments

@KouYidong
Copy link

@KouYidong KouYidong commented Jan 23, 2019

  1. 防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

  • 思路:

每次触发事件时都取消之前的延时调用方法

function debounce(fn) {
      let timeout = null; // 创建一个标记用来存放定时器的返回值
      return function () {
        clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
        timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
          fn.apply(this, arguments);
        }, 500);
      };
    }
    function sayHi() {
      console.log('防抖成功');
    }

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); // 防抖
  1. 节流

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

  • 思路:

每次触发事件时都判断当前是否有等待执行的延时函数

function throttle(fn) {
      let canRun = true; // 通过闭包保存一个标记
      return function () {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
          fn.apply(this, arguments);
          // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
          canRun = true;
        }, 500);
      };
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi));
@yygmind yygmind changed the title 节流和防抖的个人见解 第三题:节流和防抖的个人见解 Feb 12, 2019
@Carrie999

This comment has been minimized.

Copy link

@Carrie999 Carrie999 commented Feb 14, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

@barbara012

This comment has been minimized.

Copy link

@barbara012 barbara012 commented Feb 18, 2019

@Carrie999 关键在第一个参数,为了确保上下文环境为当前的this,所以不能直接用fn。

@MinosIE

This comment has been minimized.

Copy link

@MinosIE MinosIE commented Feb 18, 2019

@Carrie999 call 和 apply 可以了解一下

@yangtao2o

This comment has been minimized.

Copy link

@yangtao2o yangtao2o commented Feb 18, 2019

之前有看过一步步实现的文章,如下:

@zhongtingbing

This comment has been minimized.

Copy link

@zhongtingbing zhongtingbing commented Feb 18, 2019

@Carrie999 关键在第一个参数,为了确保上下文环境为当前的this,所以不能直接用fn。

请问为甚么你要确保fn执行的上下文是this?在这个箭头函数里this又是指向的谁?

@lilywang711

This comment has been minimized.

Copy link

@lilywang711 lilywang711 commented Feb 18, 2019

@zhongtingbing
加上 apply 确保 在 sayHi 函数里的 this 指向的是 input对象(不然就指向 window 了,不是我们想要的)。
这里的箭头函数依旧是指向 input 对象。

@lilywang711

This comment has been minimized.

Copy link

@lilywang711 lilywang711 commented Feb 18, 2019

@zhongtingbing 你去试试在 不加 apply 时去 sayHi 函数里打印下 this看看什么

是指向window的。因为 sayHi 函数定义在全局中,所以调用时里面this指向window,
所以才需要加上 apply,显示绑定 this 值(input对象)到 sayH 函数里面去

@yishuihan-001

This comment has been minimized.

Copy link

@yishuihan-001 yishuihan-001 commented Feb 19, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

@Carrie999 为了保证sayHi执行时的this指向input

@Crazy404

This comment has been minimized.

Copy link

@Crazy404 Crazy404 commented Feb 19, 2019

请问防抖那里可以写成
setTimeout(fn.bind(this), 500)
吗(小白的疑问)

@Mathround

This comment has been minimized.

Copy link

@Mathround Mathround commented Feb 20, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

估计是改变this指向

@fan-2

This comment has been minimized.

Copy link

@fan-2 fan-2 commented Feb 20, 2019

image
image
image

@ShadowWalker627

This comment has been minimized.

Copy link

@ShadowWalker627 ShadowWalker627 commented Feb 20, 2019

@zhongtingbing 你去试试在 不加 apply 时去 sayHi 函数里打印下 this看看什么

是指向window的。因为 sayHi 函数定义在全局中,所以调用时里面this指向window,
所以才需要加上 apply,显示绑定 this 值(input对象)到 sayH 函数里面去

这里用了apply确实使得this指向了input对象;对于“因为 sayHi 函数定义在全局中,所以调用时里面this指向window”,测试了一下直接使用fn(arguments)的话,在sayHi中打印this为undefined;js中this是在运行时绑定的,而不是定义时绑定的

@Liubasara

This comment has been minimized.

Copy link

@Liubasara Liubasara commented Feb 21, 2019

有个问题,假如传入的方法是异步的,上述的节流方法是没用的啊,考虑把fn.apply(this, arguments)这一句放在setTimeout外面是不是会好一点?就像下面这样。

const myThrottle2 = function (func, wait = 50) {
  var canRun = true
  return function (...args) {
    if (!canRun) {
      return
    } else {
      canRun = false
      func.apply(this, args) // 将方法放在外面, 这样即便该函数是异步的,也可以保证在下一句之前执行
      setTimeout(function () {canRun = true}, wait)
    }
  }
}
@lilywang711

This comment has been minimized.

Copy link

@lilywang711 lilywang711 commented Feb 22, 2019

@zhongtingbing 你去试试在 不加 apply 时去 sayHi 函数里打印下 this看看什么
是指向window的。因为 sayHi 函数定义在全局中,所以调用时里面this指向window,
所以才需要加上 apply,显示绑定 this 值(input对象)到 sayH 函数里面去

这里用了apply确实使得this指向了input对象;对于“因为 sayHi 函数定义在全局中,所以调用时里面this指向window”,测试了一下直接使用fn(arguments)的话,在sayHi中打印this为undefined;js中this是在运行时绑定的,而不是定义时绑定的

@Liubasara 是的,应该改为「因为 sayHi 函数是在全局中运行,所以this指向了window」,不过你说的「测试了一下直接使用fn(arguments)的话,在sayHi中打印this为undefined」是不对的哦,不显示绑定,是这里是指向window的。截图如下:
image

@KiraYo4kage

This comment has been minimized.

Copy link

@KiraYo4kage KiraYo4kage commented Feb 26, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

楼上大佬说的是对的,但是要注意这里的this(input)是addEventListener中调用回调的时候传进来的,这和是不是箭头函数没关系。
另外,因为不确定入参的数量,所以利用apply还可以传入扩展后的arguments(如果不兼容...arguments语法的话)。
已上。

@mochen666

This comment has been minimized.

Copy link

@mochen666 mochen666 commented Feb 27, 2019

@KouYidong 节流函数有点问题,第一次应该是立即执行,而不是delay 500ms后再执行

@XMoonLights

This comment has been minimized.

Copy link

@XMoonLights XMoonLights commented Mar 4, 2019

canRun和timeout的定义应该放到方法外,不然延时到了还是会执行多次

@sunnyxuebuhui

This comment has been minimized.

Copy link

@sunnyxuebuhui sunnyxuebuhui commented Mar 7, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

注意this指向问题。

@roberthuang123

This comment has been minimized.

Copy link

@roberthuang123 roberthuang123 commented Mar 7, 2019

如果单单为了打印那句console.log('防抖成功');确实可以直接fn(),但我们得考虑实际情况,让sayHi的this指向input是必要的,例如我们需要在输入完改变字体颜色,如下:
function sayHi() { console.log('防抖成功'); this.style.color = 'red'; }
这个时候fn.apply(this, arguments);的作用就显而易见了

@MiaLeung01

This comment has been minimized.

Copy link

@MiaLeung01 MiaLeung01 commented Mar 13, 2019

防抖:动作绑定事件,动作发生后一定时间后触发事件,在这段时间内,如果该动作又发生,则重新等待一定时间再触发事件。

  function debounce(func, time) {
    let timer = null;
    return () => {
      clearTimeout(timer);
      timer = setTimeout(()=> {
        func.apply(this, arguments)
      }, time);
    }
  }

节流: 动作绑定事件,动作发生后一段时间后触发事件,在这段时间内,如果动作又发生,则无视该动作,直到事件执行完后,才能重新触发。

  function throtte(func, time){
    let activeTime = 0;
    return () => {
      const current = Date.now();
      if(current - activeTime > time) {
        func.apply(this, arguments);
        activeTime = Date.now();
      }
    }
  }
@thinkfish

This comment has been minimized.

Copy link

@thinkfish thinkfish commented Mar 14, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

如果demo一中的sayHi()方法其实,没有什么区别
但是如果sayHi这个callback要改变this的指向,或者要更方便的传参的话用apply就比较方便
用call或bind也可以

这里引申的话会有俩经常会聊到的问题
1,call,apply,bind的区别
2,this的指向
这俩应该是面试必聊的问题,可以好好整理一下。博主的这个面试题的系列中这俩都有说到。

@Marze1994

This comment has been minimized.

Copy link

@Marze1994 Marze1994 commented Mar 20, 2019

@zhongtingbing 你去试试在 不加 apply 时去 sayHi 函数里打印下 this看看什么

是指向window的。因为 sayHi 函数定义在全局中,所以调用时里面this指向window,
所以才需要加上 apply,显示绑定 this 值(input对象)到 sayH 函数里面去

不加apply,sayHi里面this肯定是指向window的,但是加上apply后,fn.apply(this, arguments)这段代码里面的this的指向就要分情况讨论了,而且这个this就是sayHi里面的this。这里的情况其实指的就是setTimeout里面的回调函数是普通函数还是箭头函数。如果是箭头函数,则这里的this最终指向的是input对象,如果为普通函数,this则指向window。setTimeout关于this的问题 | MDN箭头函数 | MDN

  1. 箭头函数表现

箭头函数表现

2. 普通函数表现

普通函数表现

3. 解决办法

解决办法

@xuxb

This comment has been minimized.

Copy link

@xuxb xuxb commented Mar 26, 2019

这里似乎有个问题,就是如果使用定时器的话,在 500ms 后执行的始终是前 500ms 内触发的第一个函数 fn,之后的在 500ms 内触发函数都将被丢弃,这样的话,fn 里获取的参数 arguments 可能不准确。应该以 500ms 内触发的最后一个函数为准,而不是第一个函数。

@XDfield

This comment has been minimized.

Copy link

@XDfield XDfield commented Mar 29, 2019

防抖添加个 immediate 参数,控制直接触发还是最后触发

export function debounce(func: , wait = 500, immediate = true) {
  let timeout, context, args;
  const later = () => setTimeout(() => {
    timeout = null;
    if (!immediate) {
      func.apply(context, args)
    }
    context = args = null;
  }, wait)

  return function(this, ...params) {
    context = this;
    args = params;
    if (timeout) {
      clearTimeout(timeout);
      timeout = later();
    } else {
      timeout = later();
      if (immediate) {
        func.apply(context, args);
      }
    }
  }
}
@Jingce-lu

This comment has been minimized.

Copy link

@Jingce-lu Jingce-lu commented Mar 30, 2019

防抖:

当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。

function debounce(fn, wait = 50, immediate) {
  let timer;
  return () => {
    if (immediate) {
      fn.apply(this, arguments)
    }
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arguments)
    }, wait)
  }
}
@asd8855

This comment has been minimized.

Copy link

@asd8855 asd8855 commented Apr 12, 2019

在掘金上看到的,感觉不错 https://juejin.im/entry/58c0379e44d9040068dc952f

@hytStart

This comment has been minimized.

Copy link

@hytStart hytStart commented Apr 12, 2019

防抖 :

const deb = (fn, delay, immediate) => {
	let timer = null
	return function() {	
		const context = this
		timer && clearTimeout(timer)
		if (immediate) {
			!timer && fn.apply(context, arguments)
		}
		timer = setTimeout(() => {
                       fn.apply(context, arguments)
                }, delay)
	}
}

节流

const throttle = (fn, delay = 2000) => {
	let timer = null
	let startTime = new Date()
	return function() {
		const context = this
		let currentTime = new Date()
		clearTimeout(timer)
		if (currentTime - startTime >= delay) {
			fn.apply(context, arguments)
			startTime = currentTime
		} else {
			//让方法在脱离事件后也能执行一次
			timer = setTimeout(() => {
				fn.apply(context, arguments)
			}, delay)
		}
	}
}
@gzwgq222

This comment has been minimized.

Copy link

@gzwgq222 gzwgq222 commented Apr 14, 2019

@Liubasara

setTimeout(async () => {
   await fn.apply(this, arguments)
   canRun = true
}, time)

异步情况下这样应该就好了

@Mr-jiangzhiguo

This comment has been minimized.

Copy link

@Mr-jiangzhiguo Mr-jiangzhiguo commented Jul 30, 2019

给你们一个可以装饰class内 箭头函数的 debounce 封装

example:

class A {
  @debounce_next({delay: 400})
  scrolling = ()=>{
    console.log('hahah')
  }
}
/**
 * @description 防抖动:(decorator)可装饰类内箭头函数
 * @param {object} params - 配置
 * @param {number} params.delay - 时间阀值(单位:ms),默认:delay=300
 * @param {bool} params.immediate - 初始是否立刻执行,默认:immediate=false
 * @returns {function} - 返回装饰器方法
 */
export const debounce_next = (params = {}) => {
  // reference:http://es6.ruanyifeng.com/#docs/decorator#%E6%96%B9%E6%B3%95%E7%9A%84%E4%BF%AE%E9%A5%B0
  return function(target, name, descriptor) {
    let timer = null;
    const { delay = 300, immediate = false } = params;

    // high order function
    if (!descriptor || (arguments.length === 1 && typeof target === 'function')) {
      return createDebounce(target);
    }

    function createDebounce(fn) {
      return function debounce() {
        if (immediate && !timer) {
          fn.apply(this, arguments);
        }
        if (timer) {
          clearTimeout(timer);
        }

        let argumentsCopy = arguments;
        let that = this;

        timer = setTimeout(function() {
          if (!immediate) {
            fn.apply(that, argumentsCopy);
          }
          timer = null;
        }, delay);
      };
    }

    // 修饰类内的箭头函数
    if (descriptor.initializer) {
      return {
        enumerable: false,
        configurable: true,
        get: function() {
          return createDebounce(descriptor.initializer.call(this));
        },
      };
    }

    return descriptor;
  };
};
@wsq815

This comment has been minimized.

Copy link

@wsq815 wsq815 commented Jul 31, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

因为要绑定fn的this指向,在setTimeout里面执行的函数都会在全局作用域执行所以this会指向window,但是fn执行时this指向的上下文环境应该是当前执行的函数,所以要用apply来绑定fn的this指定到当前

@Carson126

This comment has been minimized.

Copy link

@Carson126 Carson126 commented Aug 9, 2019

请问防抖那里可以写成
setTimeout(fn.bind(this), 500)
吗(小白的疑问)

你argument怎么解决?

@quxian

This comment has been minimized.

Copy link

@quxian quxian commented Aug 16, 2019

基于事件的,感觉更优雅。

const debounce = (eventTarget, eventName, wait) => {
    let handle;
    let event = new Event(eventName);
    return (detail) => {
        event.detail = detail;
        clearTimeout(handle);
        handle = setTimeout(() => eventTarget.dispatchEvent(event), wait);
    }
}

const throttle = (eventTarget, eventName, wait) => {
    let isRuning = false;
    let event = new Event(eventName);
    return (detail) => {
        if(isRuning) return;
        isRuning = true;
        setTimeout(() => isRuning = false, wait);
        event.detail = detail;
        eventTarget.dispatchEvent(event);
    }
}

let target = window;

target.addEventListener('resize', debounce(target, 'myonresizedebounce', 3000))

target.addEventListener('myonresizedebounce', (event) => {
    console.log({name: 'myonresizedebounce', event});
})

target.addEventListener('resize', throttle(target, 'myonresizethrottle', 1000))

target.addEventListener('myonresizethrottle', (event) => {
    console.log({name: 'myonresizethrottle', event});
})
@pekonchan

This comment has been minimized.

Copy link

@pekonchan pekonchan commented Aug 17, 2019

不能简单掌握基础的就够了。面试要有亮点。不然基础的知识大家都回答了,面试官只能找有特色的了。

可以看看这个
别只掌握基础的防抖和节流了

@aeolusheath

This comment has been minimized.

Copy link

@aeolusheath aeolusheath commented Aug 27, 2019

防抖:
一定时间内只执行一次,若这段时间内发起新的动作,那么时间从头开始计时。比如时间设置为5s,只要动作间隔时间小于5s,那么这个动作不会发生,会等待最后一次动作之后5s触发。

    const debounce = (fn, dur = 500) => {
      let timer = null
      return function(...args) {
        clearTimeout(timer)
        timer = setTimeout(()=>{
          fn.apply(this, args)
        }, timer)
      }
    }

节流:
一定时间内只执行一次,若这段时间内发起新的动作,无视之。每隔多少秒执行一次。

  const throttle = (fn, dur = 5000) => {
    let timer = null
    return function(...args) {
      if (timer == null) {
         fn.apply(this, args)   // 放在这里是触发动作后马上执行,5s后执行下一次动作。
         timer = setTimeout(()=> {
            // fn.apply(this, args)   // 放在这里是触发动作5s后执行,然后5s后执行下一次动作。
            timer = null
        })
      }
    }
  }
@xiaoxixi6633

This comment has been minimized.

Copy link

@xiaoxixi6633 xiaoxixi6633 commented Sep 12, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

this

为了 给input 绑定 事件

@xiaoxixi6633

This comment has been minimized.

Copy link

@xiaoxixi6633 xiaoxixi6633 commented Sep 12, 2019

function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}

上面的return function () { 这样写的目的到底是为了闭包保存timeout 如果是为了保存timeout 那为申明要手动销毁

@gyf940349398

This comment has been minimized.

Copy link

@gyf940349398 gyf940349398 commented Sep 18, 2019

防抖

原理:在每次函数执行之前先清空上一次设置的定时器,原因是:如果执行间隔大于n秒,那么先前的定时任务一定已经执行完毕,当执行clearTimeout时其实并没有定时器可清除;否则定时器就会被清除,然后重新计时

function debounce(fn, wait, immediate) {
    var timer = null;
    return function() {
        var context = this;
        var args = arguments;

        // 判断首次是否需要立即执行
        if (immediate) {
            fn.call(context, ...args);
            immediate = false;
        }

        // 清除定时器
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn.call(context, ...args);
        }, wait);
    };
}

节流

原理:在每次函数执行之前先判断是否存在定时器,存在则跳过本次执行,否则设置新的定时器

function throttle(fn, wait, immediate) {
    var timer = null;
    return function() {
        var context = this;
        var args = arguments;

        // 判断首次是否需要立即执行
        if (immediate) {
            fn.call(context, ...args);
            immediate = false;
        }

        // 如果当前存在定时器,返回;否则设置定时器
        if (timer) return;

        timer = setTimeout(function() {
            fn.call(context, ...args);
            // 函数执行完毕后,清除定时器
            clearTimeout(timer);
            timer = null;
        }, wait);
    };
}
@wangziqi0503

This comment has been minimized.

Copy link

@wangziqi0503 wangziqi0503 commented Sep 22, 2019

问个小白的问题,为什么要return一个函数出来,不然就会直接执行出第一次的结果,就再也触发不了了

@MXXXXXS

This comment has been minimized.

Copy link

@MXXXXXS MXXXXXS commented Sep 23, 2019

使用 timer 或 Date 都可以实现
写了个demo
防抖和节流实现

@224137748

This comment has been minimized.

Copy link

@224137748 224137748 commented Sep 29, 2019

我工作中主要的应用场景,
1、防抖:在搜索输入框中,根据用户输入内容变化动态展示联想搜索词
2、节流:抢票啊、提交数据、切换、轮播和部分Animate动画中,执行完上一次操作后才能再次点击执行对应的函数

@mingzhaocn

This comment has been minimized.

Copy link

@mingzhaocn mingzhaocn commented Oct 5, 2019

// 防抖是在指定时间内再次触发只会响应一次(最后那次)表现为延迟调用
const debounceIfy = (fn, wait) => {
  let timeoutId;
  return (...args) => {
    if (timeoutId) {
      // timeoutId 如果存在说明上一个fn任务还在等待,应该重制它为最近一次调用
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => {
      fn(...args);
    }, wait);
  };
};

const TestDebounceIfy = () => {
  console.log('====TestDebounceIfy====');
  const fn = count => {
    console.log(count);
  };
  const wait = 500;
  const debounceFn = debounceIfy(fn, wait);

  debounceFn(1);
  debounceFn(2);
  debounceFn(3);
  setTimeout(() => {
    debounceFn(4);
  }, wait * 0.4);
  setTimeout(() => {
    debounceFn(5);
  }, wait * 1);
  setTimeout(() => {
    debounceFn(6);
  }, wait * 1.2);

  setTimeout(() => {
    debounceFn(7);
  }, wait * 2.4); // 只有距上一次间隔大于1个wait(2.4-1.2>1)才会被调用
};

// TestDebounceIfy();
// 6
// 7

//节流是指让事件的触发按指定速率触发,表现为立即调用但控制速率
const throttleIfy = (fn, time) => {
  let canRun = true;
  return (...args) => {
    if (canRun) {
      canRun = false;
      fn(...args);
      setTimeout(() => {
        canRun = true; // time 过后对 fn 放行
      }, time);
    }
  };
};

const TestThrottleIfy = () => {
  console.log('====TestThrottleIfy====');
  const fn = count => {
    console.log(count);
  };
  const time = 500;
  const throttleFn = throttleIfy(fn, time);

  throttleFn(1);
  throttleFn(2);
  throttleFn(3);
  setTimeout(() => {
    throttleFn(4);
  }, time * 0.4);
  setTimeout(() => {
    throttleFn(5);
  }, time * 1);
  setTimeout(() => {
    throttleFn(6);
  }, time * 1.2);

  setTimeout(() => {
    throttleFn(7);
  }, time * 2.4);
};

// TestThrottleIfy(); // 每过一个 time 放行一次
// 1
// 5
// 7
@2384830985

This comment has been minimized.

Copy link

@2384830985 2384830985 commented Oct 12, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

可以this 指向 以及当前的数据也都传递给下面都方法

@git-git-hup

This comment has been minimized.

Copy link

@git-git-hup git-git-hup commented Oct 21, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()
考虑到还有参数的情况下,改变this指向

@NARUTOne

This comment has been minimized.

Copy link

@NARUTOne NARUTOne commented Oct 25, 2019

节流

n秒内只会执行一次,节流会稀释函数的执行频率

/**
 * @name throttle, 节流函数,(高频操作,性能优化)
 * @param {*} func
 * @param {number} [wait = 300]
 * @returns
 * @example
 * 
 *  异步使用
 const throttleAjax = throttle((newParams)=>{
  return new Promise((resolve, reject) => {
    xhr({
      url: '/api',
      type: 'POST',
      data: newParams,
      success: res => {
        const {data} = res;
        const arr = isArray(data) ? data : [];
        resolve(arr);
      },
      error: err => {
        reject(err);
      }
    });
  });
}, 300);
export function apiExample (params) {
  const newParams = filterParams(params);
  return new Promise((resolve) => {
    const keys = Object.keys(newParams);
    if (!keys.length) {
      resolve([]);
    } else {
      throttleAjax(newParams).then(res => {
        resolve(res);
      });
    }
  });
}
 */

function throttle (func, wait = 300) {
  var timeout;
  return function () {
    var context = this;
    var args = arguments;
    return new Promise((resolve) => {
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null;
          resolve(func.apply(context, args));
        }, wait);
      }
    });
  };
}

export default throttle;

防抖

每次触发事件时都取消之前的延时调用方法

/**
 * @name debounce, 防抖函数,(高频操作,性能优化)
 * @param {*} fn
 * @param {number} [step=100]
 * @returns
 * @example
 * /
function debounce(fn, step = 100) {
  let timeout = null;
  return function () {
    clearTimeout(timeout);
    return new Promise(resolve => {
      timeout = setTimeout(() => {
        resolve(fn.apply(this, arguments));
      }, step);
    });
  };
}

export default debounce;
@ForeseeBear

This comment has been minimized.

Copy link

@ForeseeBear ForeseeBear commented Oct 29, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

fn.apply(this,arguments); 当前是this;fn()的this是window

@barnett617 barnett617 mentioned this issue Nov 5, 2019
@Tinet-zhangmd

This comment has been minimized.

Copy link

@Tinet-zhangmd Tinet-zhangmd commented Nov 13, 2019

请问,为什么要 fn.apply(this, arguments);而不是这样 fn()

fn.apply(this,arguments) 这里的this指的也是window吧?fn()的this也是window呀?有什么区别吗?

@xgqfrms

This comment has been minimized.

Copy link

@xgqfrms xgqfrms commented Nov 18, 2019

debounce

防抖,是指一个事件触发后在单位时间内,如果发生重复触发了同一事件,则取消上一次的事件,并重新计时⌛️

(后面触发的事件执行,替代了前面的事件)

throttle

节流,是指在单位时间内, 只会触发一次事件,如果事件触发后,又重复触发了同一事件,则忽略后面触发的事件,直到第一次事件的计时⌛️结束

(前面触发的执行前,忽略后面的事件)

@tomwang1013

This comment has been minimized.

Copy link

@tomwang1013 tomwang1013 commented Nov 20, 2019

有个问题,假如传入的方法是异步的,上述的节流方法是没用的啊,考虑把fn.apply(this, arguments)这一句放在setTimeout外面是不是会好一点?就像下面这样。

const myThrottle2 = function (func, wait = 50) {
  var canRun = true
  return function (...args) {
    if (!canRun) {
      return
    } else {
      canRun = false
      func.apply(this, args) // 将方法放在外面, 这样即便该函数是异步的,也可以保证在下一句之前执行
      setTimeout(function () {canRun = true}, wait)
    }
  }
}

解释下呢,func异步不异步有啥影响?
不过你这种写法倒是有个优点,就是函数的执行时机与其调用时机基本吻合,不会延迟执行

@tomwang1013

This comment has been minimized.

Copy link

@tomwang1013 tomwang1013 commented Nov 20, 2019

这里似乎有个问题,就是如果使用定时器的话,在 500ms 后执行的始终是前 500ms 内触发的第一个函数 fn,之后的在 500ms 内触发函数都将被丢弃,这样的话,fn 里获取的参数 arguments 可能不准确。应该以 500ms 内触发的最后一个函数为准,而不是第一个函数。

@Liubasara 的回复就没有这个问题,把函数的执行放到timeout之外去,timeout单纯做计时作用就行

@wgl758

This comment has been minimized.

Copy link

@wgl758 wgl758 commented Dec 12, 2019

请问防抖那里可以写成
setTimeout(fn.bind(this), 500)
吗(小白的疑问)

不可以。必须得清除定时器,不然,就不是防抖,而是把抖动滞后了。

@yygmind yygmind added the JS基础 label Dec 16, 2019
@bbrucechen

This comment has been minimized.

Copy link

@bbrucechen bbrucechen commented Dec 25, 2019

请问大佬们,如果问节流和防抖的区别,我可不可以回单说两者本质上都是稀释了事件的执行频率,但是防抖是通过取消上一次事件的执行而稀释,而节流是阻止下次事件的执行而稀释?

@zowiegong

This comment has been minimized.

Copy link

@zowiegong zowiegong commented Jan 7, 2020

下图能很清楚的演示在 mousemove 情况下

防抖(debounce)和节流(throttle)表现上的差异。

16f6eba8e2d1fc04

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.