JavaScript 防抖与节流
创始人
2024-02-02 11:49:50

目录

  • 1 函数
    • 1.1 调用函数
    • 1.2 闭包
  • 2 防抖与节流
    • 2.1 定义
    • 2.2 区别
    • 2.3 应用场景
  • 3 防抖
    • 3.1 非立即执行
      • 3.1.1 一般写法
      • 3.1.2 Vue2 中写法
      • 3.1.3 过程
    • 3.2 立即执行
      • 3.2.1 一般写法
      • 3.2.2 Vue2 中写法
      • 3.2.3 过程

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中写法总结)

相关内容

热门资讯

脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...