目录
- 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 方法
- 调用函数加括号
fn()
:执行函数体 fn,执行后得到其返回值 - 调用函数不加括号
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 区别
- 防抖是将多次执行变成最后一次执行;
- 节流是将多次执行变为每隔一段时间执行一次。
正常执行 | run | run | run | run | run | run | run | run | — | — |
防抖: 非立即执行 | — | — | — | — | — | — | — | 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中写法总结)