【JavaScript】 防抖与节流

1 函数

应用防抖节流首先需理解以下知识

1.1 调用函数

js 函数内部 return 一个函数,自动调用 toString 方法

  1. 调用函数加括号 fn():执行函数体 fn,执行后得到其返回值
  2. 调用函数不加括号 fn:不会执行函数体,而是得到函数体的源码。
    • 函数名其实就是指向函数的指针,它只是传递了函数体所在的地址位置,在需要执行时找到函数体去执行。

1.2 闭包

JS 中 return 一个函数与直接 return 一个函数变量的区别
函数的节流与防抖

function makeCounter() {
  var count = 0;
  function counter() {
    count = count + 1;
    return count;
  }
  return counter(); // 将嵌套函数返回
}
var doCount = makeCounter();
console.log(doCount, "--doCount1"); // 1 '--doCount1'
console.log(doCount, "--doCount2"); // 1 '--doCount2'
console.log(doCount, "--doCount3"); // 1 '--doCount3'
  • 当 return counter()时,就自动调用了嵌套函数。
  • 那么嵌套函数返回一个经过+1 的 count,并且 count 的值为 1.
  • 所以 doCount 得到的是一个数字,并不是函数,所以无法得到闭包。
function makeCounter() {
  var count = 0;
  function counter() {
    count = count + 1;
    return count;
  }
  return counter; // 将嵌套函数返回,但只写函数名称
}
var doCount = makeCounter();
console.log(doCount(), "--doCount1"); // 1 '--doCount1'
console.log(doCount(), "--doCount2"); // 2 '--doCount2'
console.log(doCount(), "--doCount3"); // 3 '--doCount3'
  • return counter 返回的是整一个 cunter()函数。

  • 因此执行 var doCount = makeCounter()时,doCount 将引用 counter 函数及其中的变量环境。

  • 那么 counter 函数及其中的变量环境,就是闭包了

  • 闭包的形成:内部函数引用了外部函数的数据(这里为 count),

  • 因此在 doCount=makeCounter(),则会把这个 count 保存在 doCount 中,函数执行完,count 并不会被销毁。

  • 注意: 为什么上面这段代码没有直接写的 function doCount(){…} 而是把 function 赋值给了变量 doCount 呢?

  • 我们通常会想当然的认为每次调用 doCount() 都会重走一遍 doCount()中的代码块, 但其实不然。

  • 注意 makeCounter 方法中的 return 不是 1,2,3 这样的数值, 而是一个方法,并且把这个方法赋值给了 doCount 变量。

  • 那么在这个 makeCounter 自运行一遍之后, 其实最后赋值给 doCount 的是 count = count + 1; return count; 这段代码。

  • 所以后面每次调用 doCount() 其实都是在调用 count = count + 1; return count;

  • 闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个 count 其实就是来自于第一次 makeCounter 执行时创建的变量

2 防抖与节流

什么是防抖和节流?有什么区别?如何实现?
防抖节流详细用法和区别 - 详解版

2.1 定义

  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

  • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

  • 一个经典的比喻:

    • 想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
    • 假设电梯有两种运行策略 debounce 和 throttle,超时设定为 15 秒,不考虑容量限制
    • 电梯第一个人进来后,15 秒后准时运送一次,这是节流
    • 电梯第一个人进来后,等待 15 秒。如果过程中又有人进来,15 秒等待重新计时,直到 15 秒后开始运送,这是防抖

2.2 区别

  • 防抖是将多次执行变成最后一次执行;
  • 节流是将多次执行变为每隔一段时间执行一次。
正常执行runrunrunrunrunrunrunrun
防抖: 非立即执行run
防抖: 立即执行run

在这里插入图片描述
在这里插入图片描述

2.3 应用场景

  • 防抖在连续的事件,只需触发一次回调的场景有:
    • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
    • 手机号、邮箱验证输入检测
    • 窗口大小 resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
    • 登录、
    • 点击按钮提交表单、
    • 点赞、收藏、标心…
  • 节流在间隔一段时间执行一次回调的场景有:
    • 滚动加载,加载更多或滚到底部监听
    • 搜索框,搜索联想功能,隔一段时间就请求
    • scroll 滑动事件、
    • resize 浏览器窗口改变事件、
    • mousemove 鼠标移动事件、
    • 文档编辑隔一段时间自动保存…

3 防抖

防抖有两种实现方式:非立即执行版 和 立即执行版。

  • 非立即执行版:事件触发 -> 延时 -> 执行回调函数。
    • 如果延时过程中事件再次被触发,则请控制之前计时器重新计时。没有触发则等延迟结束后,执行回调函数。
  • 立即执行版:事件触发 -> 立即执行 -> 延时。
    • 延时过程中事件被触发则继续执行延时,延时结束后不会执行回调函数(比如表单提交,点赞,收藏)。

3.1 非立即执行

3.1.1 一般写法

Function.prototype.apply()

// 1
function debounce(func, delay) {
  let timeout;
  return function () {
    let context = this;
    let args = arguments;

    if (timeout) {
      clearTimeout(timeout);
    }

    timeout = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}
// 2
function debounce(func, delay) {
  let timeout;
  return () => {
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(func, delay);
  };
}
// 使用
debounce(() => {
  console.log("run");
}, 500);

3.1.2 Vue2 中写法

【记】Vue 中使用防抖函数所遇见的坑

Vue 中使用时,需要定义 timeout,同时在防抖函数中,this 的指向发生了变化,需要在 return 之前获取 vue 实例。
这个时候,你直接使用,还是不行的,只要 debug 就会发现 debounce 返回的 func 没有进去,需要手动执行一下(添加括号)。

要把 timeout 挂在 this 上,否则不起作用

data() {
  return {
    timeout: null // 关键在这里
  }
}
// 1
methods: {
  debounce(func, delay) {
    let context = this; // this指向发生变化,需要提出来
    let args = arguments;
    return (function () {
      if (context.timeout) {
        clearTimeout(context.timeout);
      }

      context.timeout = setTimeout(() => {
        func.apply(context, args);
      }, delay);
    })(); // 注意这里有()
  },
}
// 2
methods: {
  debounce(func, delay) {
    let context = this; // this指向发生变化,需要提出来
    return (function () {
      if (context.timeout) {
        clearTimeout(context.timeout);
      }

      context.timeout = setTimeout(() => {
        func();
        context.timeout = null; // 必须要清空,否则影响另一事件
      }, delay);
    })(); // 注意这里有()
  },
}
// 使用
debounce(() => {
  console.log("run");
}, 500);

若要封装成公共方法,把 context 作为参数

export const debounce = (func, context, delay = 500) => {
  return (function () {
    if (context.timeout) {
      clearTimeout(context.timeout);
    } else {
      func();
    }
    context.timeout = setTimeout(() => {
      context.timeout = null;
    }, delay);
  })();
};
// 选择式 API
// 使用
debounce(
  () => {
    console.log("run");
  },
  this,
  500
);

3.1.3 过程

var count = 1;

// 非立即执行
export const debounce = (func, context, delay = 500) => {
  console.log(count, "--count--1");

  return (function () {
    console.log(context.timeout, "--context.timeout--1");

    if (context.timeout) {
      clearTimeout(context.timeout);
      console.log(context.timeout, "--context.timeout--2");
    }
    context.timeout = setTimeout(func, delay);
    console.log(context.timeout, "--context.timeout--3");

    count += 1;
    console.log(count, "--count--2");
  })();
};

连续点击 3 次

1 '--count--1'
null '--context.timeout--1'
110 '--context.timeout--3'
2 '--count--2'
2 '--count--1'
110 '--context.timeout--1'
110 '--context.timeout--2'
116 '--context.timeout--3'
3 '--count--2'
3 '--count--1'
116 '--context.timeout--1'
116 '--context.timeout--2'
122 '--context.timeout--3'
4 '--count--2'
--ok

前两次 func 不执行,因为被 clearTimeout 了

3.2 立即执行

3.2.1 一般写法

// 1
function debounce(func, delay) {
  let timeout;
  return function () {
    let context = this;
    let args = arguments;

    if (timeout) {
      clearTimeout(timeout);
    }

    timeout = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}
// 2
function debounce(func, delay) {
  let timeout;
  return () => {
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(func, delay);
  };
}
// 使用
debounce(() => {
  console.log("run");
}, 500);

3.2.2 Vue2 中写法

data() {
  return {
    timeout: null
  }
}
// 1
methods: {
  debounce(func, delay) {
    let context = this; // this指向发生变化,需要提出来
    let args = arguments;
    return (function () {
      if (context.timeout) {
        clearTimeout(context.timeout);
      } else {
        func.apply(context, args);
      }
      context.timeout = setTimeout(() => {
        context.timeout = null;
      }, delay);
    })(); // 注意这里有()
  },
}
// 2
methods: {
  debounce(func, delay) {
    let context = this; // this指向发生变化,需要提出来
    return (function () {
      if (context.timeout) {
        clearTimeout(context.timeout);
      } else {
        func();
      }
      context.timeout = setTimeout(() => {
        context.timeout = null;
      }, delay);
    })(); // 注意这里有()
  },
}
// 使用
debounce(() => {
  console.log("run");
}, 500);

若要封装成公共方法,把 context 作为参数

export const debounce = (func, context, delay = 500) => {
  return (function () {
    if (context.timeout) {
      clearTimeout(context.timeout);
    } else {
      func();
    }
    context.timeout = setTimeout(() => {
      context.timeout = null;
    }, delay);
  })();
};
// 选择式 API
// 使用
debounce(
  () => {
    console.log("run");
  },
  this,
  500
);

3.2.3 过程

var count = 1;

export const debounce = (func, context, delay = 500) => {
  console.log(count, "--count--1");
  return (function () {
    console.log(context.timeout, "--context.timeout--1");

    if (context.timeout) {
      clearTimeout(context.timeout);
      console.log(context.timeout, "--context.timeout--2");
    } else {
      func();
      console.log("--func");
    }
    context.timeout = setTimeout(() => {
      context.timeout = null;
      console.log(context.timeout, "--context.timeout--4");
    }, delay);
    console.log(context.timeout, "--context.timeout--3");

    count += 1;
    console.log(count, "--count--2");
  })();
};

延时器方法 setTimeout () 的返回值是一个代表定时器唯一身份标识的编号

连续点击 4 次结果

1 '--count--1'
null '--context.timeout--1'
--func
126 '--context.timeout--3'
2 '--count--2'
2 '--count--1'
126 '--context.timeout--1'
126 '--context.timeout--2'
132 '--context.timeout--3'
3 '--count--2'
3 '--count--1'
132 '--context.timeout--1'
132 '--context.timeout--2'
138 '--context.timeout--3'
4 '--count--2'
4 '--count--1'
138 '--context.timeout--1'
138 '--context.timeout--2'
138 '--context.timeout--3'
5 '--count--2'
null '--context.timeout--4'
  • 最后才输出 null '--context.timeout--4' 因为前面的几个定时器 都被 clearTimeout 了,不会执行

JavaScript 防抖 (vue中写法总结)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值