一、在前端开发当中,有些交互事件,会频繁触发。这样会导致我们的页面渲染性能,如果频繁触发接口调用的话,会直接导致服务器性能的浪费。
例如:键盘事件 keyup作为测试
- <ul>
- <li>
- 未做处理:<input type="text" id="pt">
- <div>函数被调用:<span id="count">0</span>次</div>
- </li>
- </ul>
- const count = document.getElementById('count');
- const pt = document.getElementById('ipt');
- let init = 0
- pt.onkeyup = function() {
- count.innerText = ++init
- }
运行效果:

每次输入都会触发事件的执行。如果我们用这样的方式去检测:当前用户输入的用户名是否可用?如此高频率的触发不仅是极大的浪费,而且用户还没有输入完成就检测,对用户的提示也不好。应该等用户输入完了,我们再去触发函数,下面我们优化一下:
- <ul>
- <li>
- 未做处理:<input type="text" id="pt">
- <div>函数被调用:<span id="count">0</span>次</div>
- </li>
- <li>
- 防抖处理:<input type="text" id="pt2">
- <div>函数被调用:<span id="count2">0</span>次</div>
- </li>
- </ul>
- const Count2 = document.getElementById('count2');
- const pt2 = document.getElementById('pt2');
- // 设置一个默认值 500ms
- const debounce = (fn, wait = 500) => {
- let time = null
- return function(arguments) {
- const _this = this, args = arguments
- clearTimeout(time)
- time = setTimeout(() => {
- fn.apply(_this, [args])
- }, wait)
- }}
- let init2 = pt2.onkeyup = debounce(function() {
- Count2.innerText = ++init2
运行效果:

可以看到,加了防抖函数之后,当我们在频繁输入的时候,函数并没有执行, 只有在函数指定的间隔内(500ms)不再输入了,才会执行函数。如果在时间间隔之内继续输入,会触发函数重新计数。
函数防抖:在事件触发后的n秒之后,再去执行真正需要执行的函数,如果在这n秒之内事件又被触发,则重新开始计时。
也就是说,如果用户在间隔时间内一直触发函数,那么这个防抖函数内部的真正需要执行的函数将永远无法执行。
那有没有什么好点的办法,让用户在输入过程中,既能触发真实需要的函数,又能达到优化的效果?
答案是肯定的,那就是:
函数截流:规定好一个单位时间,触发函数一次。如果在这个单位时间内触发多次函数的话,只有一次是可被执行的。想执行多次的话,只能等到下一个周期里。
- <li>
- 截流处理:<input type="text" id="pt3">
- <div>函数被调用:<span id="count3">0</span>次</div>
- <div>当前时间(分/秒):<span id="time"></span></div>
- </li>
- const count3 = document.getElementById('count3');
- const pt3 = document.getElementById('pt3');
- const time = document.getElementById('time');
- const throttle = (fn, hold = 1000) => {
- let last, deferTimer
- return function(arguments) {
- const _this = this, args = arguments
- let now = +new Date()
- if(last && now < last + hold) {
- clearTimeout(deferTimer)
- deferTimer = setTimeout(function () {
- last = now
- fn.call(_this, args)
- }, hold)
- } else {
- last = now
- fn.call(_this, args)
- }
- }}
- let init3 = 0
- const Ipt = throttle(function() {
- let time = new Date().getMinutes() + ':' + new Date().getSeconds()
- time.innerText = time
- count3.innerText = ++init3
- }, 1000) // 初始化一下
-
- oIpt3.onkeyup = function() {
- Ipt()
- }
二、应用场景:防抖和截流都是用来防止高频率的js代码的执行
1、防抖:防抖本质上就是以最后的操作为标准
例如:此时此刻我们都在排队等公交,司机说必须等到坐满才会发车,这时候
的参照标准就是最后一个人上车,公交车好比我们的js代码,最后一个人就充当我们的
执行条件。例如:
- let setTimer;
- let shake = function() {
- clearTimeout(setTimer);
- setTimer = setTimeout(() => {
- console.log("这里是实际的业务代码");
- }, 0);
- };
- let interTimer = setInterval(() => {
- shake();
- }, 0);
- let timer = setTimeout(() => {
- clearInterval(interTimer);
- clearTimeout(timer);
- timer = null;
- interTimer = null;
- }, 2000);
执行以上代码,控制台会在 2s 后打出日志,2s 之内的操作都被清空,以最后一次的操作为准。
如果监听滚动事件,假设两秒以内用户在不断的平凡的触发onScroll事件,只有用户暂停滚动后,才会去执行响应的操作,代码如下:
- // 函数防抖
- var timer = false;
- document.getElementById("xxxx").onscroll = function(){
- clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
- timer = setTimeout(function(){
- console.log("函数防抖");
- }, 300);
- };
2、截流:
(1)定时器实现截流
- let isAllow = true;
- function shake() {
- let fun = function() {
- if (!isAllow) return;
- isAllow = false;
- let timer = setTimeout(() => {
- console.log("这里是实际的业务代码");
- clearTimeout(timer);
- timer = null;
- isAllow = true;
- }, 1000);
- };
- fun();
- }
- let interTimer = setInterval(() => {
- shake();
- }, 0);
执行以上会看到控制台每隔 1s 后打印出结果,1s 内不会执行打印日志
(2)闭包实现函数截流:
- // fn是我们需要包装的事件回调, interval是时间间隔的阈值
- function fun(fn, interval) {
- let last = 0; // last为上一次触发回调的时间
- // 将throttle处理结果当作函数返回
- return function() {
- let context = this; // 保留调用时的this上下文
- let args = arguments; // 保留调用时传入的参数
- let now = +new Date(); // 记录本次触发回调的时间
- // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
- if (now - last >= interval) {
- // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
- last = now;
- fn.apply(context, args);
- }
- };
- }
- // 用fun来包装scroll的回调
- const better_scroll = fun(() => console.log("触发了滚动事件"), 1000);
- setInterval(() => better_scroll(), 0);
-