应用防抖节流首先需理解以下知识
js 函数内部 return 一个函数,自动调用 toString 方法
fn()
:执行函数体 fn,执行后得到其返回值fn
:不会执行函数体,而是得到函数体的源码。
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'
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
执行时创建的变量
什么是防抖和节流?有什么区别?如何实现?
防抖节流详细用法和区别 - 详解版
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
一个经典的比喻:
正常执行 | run | run | run | run | run | run | run | run | — | — |
防抖: 非立即执行 | — | — | — | — | — | — | — | run | — | — |
防抖: 立即执行 | run | — | — | — | — | — | — | — | — | — |
防抖有两种实现方式:非立即执行版 和 立即执行版。
// 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);
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
);
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 了
// 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);
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
);
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 了,不会执行