应用场景
防抖(debounce)
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流(throttle)
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 懒加载监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
防抖实现
多次触发的事件,在设定的时间间隔内,只执行最后一次,或者只执行最开始的一次,如果时间间隔没到又触发,那就重新开始计时。举个例子,比如时间间隔设为1秒钟,第一次触发会开始计时,如果在1秒内再次触发,就会重新计时,只有1秒内没有再触发才会执行一次。
下面是高程中的经典代码,第一次执行的时候宏任务队列为空,没有setTimeout,所以clearTimeout没有作用,然后setTimeout被加入宏任务队列。如果没有继续触发,那么过500毫秒后将执行这个方法。如果在500毫秒内触发,那么之前添加进宏任务队列的setTimeout会被clearTimeout给去掉,然后将新的setTimeout推入宏任务队列,然后重新开始计时。
function debounce(method, context) {
clearTimeout(method.tId);
method.tId = setTimeout(function() {
method.call(context)
}, 500)
}
使用闭包记录一下是否被触发过,然后匿名函数在全局下执行会导致this和arguments丢失,所以同样需要使用闭包保存一下。
非立即执行版:触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
function debounce(func, wait) {
let timer;
return function() {
let that = this; // 注意 this 指向
let args = arguments; // arguments中存着e
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(that, args)
}, wait)
}
}
content.onmousemove = debounce(count,1000);
立即执行版:触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果
function debounce(func, wait) {
let timer;
return function() {
let that = this; // 这边的 this 指向谁?
let args = arguments; // arguments中存着e
if (timer) clearTimeout(timer);
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait)
if (callNow) func.apply(that, args);
}
}
节流实现
节流就是将单位时间内的多次触发合并为一次,即n秒内执行一次,2n秒内执行2次。
同样需要使用闭包,一是记录上一次触发的时间,二是保存函数执行的this和arguments
时间戳版
function throttle(func, wait) {
let previous = 0;
return function() {
let now = Date.now();
let that = this;
let args = arguments;
if (now - previous > wait) {
func.apply(that, args);
previous = now;
}
}
}
content.onmousemove = throttle(count,1000);
定时器版
function throttle(func, wait) {
let timeout;
return function() {
let that = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(that, args)
}, wait)
}
}
}
关于闭包中this指向问题
首先明确一下,函数中 this 永远指向调用它的对象,谁调用它就指向谁。
使用 obj
对象调用 getName
,因此 this 指向 obj
对象,obj.getName()
输出 My obj
。
第二个 foo
函数实际上是由顶层 window
对象调用的,等价于 this.foo()
,因此 this
指向 window
对象,foo()
输出 The Window
。
var name = "The Window";
var obj = {
name: 'My obj',
getName: function() {
console.log(this.name);
}
}
obj.getName(); // My obj
const foo = obj.getName;
foo(); // The Window
因此在下面的代码中,当使用 obj
去调用 getName
方法时,getName
方法的 this 指向 obj
对象。
但是 getName
方法里面还返回了另一个函数,且这是一个匿名函数,匿名函数的执行环境具有全局性,因此匿名函数中的 this 常常指向 window 。
var name = "The Window";
var obj = {
name: 'My obj',
getName: function() {
console.log(this.name); // My obj
return function() {
console.log(this.name); // The Window
}
}
}
obj.getName()();
var 声明的变量会挂载到 window 对象上,因此可以使用 this 访问
为了防止匿名函数中的 this 丢失,一种解决方案是使用闭包将 this 保存一下。
var name = "The Window";
var obj = {
name: 'My obj',
getName: function() {
var that = this; // 使用 that 变量保存 this
return function() {
console.log(this.name); // My obj
}
}
}
obj.getName()();
还有一种方法是使用 ES6 中的箭头函数。跟匿名函数一样,箭头函数没有自己的 this ,但是箭头函数会自动捕获并固定上级词法作用域的 this 。
var name = "The Window";
var obj = {
name: 'My obj',
getName: function() {
return () => {
console.log(this.name); // My obj
}
}
}
obj.getName()();