- 参考文档1讲解不错,结合参考文档2一起看;
- 本文另补充了在防抖和节流闭包中,分别使用箭头函数、普通函数中的this指向问题
- 不管防抖还是节流,要记得用闭包来保存定时器;
参考文档1:github.com/Advanced-Fr…
防抖
- 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
- 思路:每次触发事件时都取消之前的延时调用方法
// github版 --- 看这个
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
// 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
timeout = setTimeout(() => {
console.log('arguments', arguments)
fn.apply(this, arguments); // arguments是指向我也不知道???
}, 5000);
};
}
function sayHi(a) {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
// arg 加了参数版
function debounce(fn, arg) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
// 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
timeout = setTimeout(() => {
console.log('---arg', this, arg) // this:input
fn.apply(this, [arg]);
}, 500);
};
}
function sayHi(arg) {
console.log('防抖成功', arg);
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, {a:'aaaa'})); // 防抖
节流
- 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
- 思路:每次触发事件时都判断当前是否有等待执行的延时函数
// git版 -- 看这个
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
// 当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
// blog版,直接通过 timer来判断
function throttle (f, wait) {
let timer
return (...args) => {
if (timer) { return }
timer = setTimeout(() => {
f(...args)
timer = null
}, wait)
}
}
通过节流引发的 this指向总结大全
- 代码1)中this指向 input --- 因为debounce的返回值其实就是 inp.addEventListener('input')的回调函数,事件处理函数中的 this指向 DOM
function debounce(fn, arg) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
console.log('---1this', this) //1) this指向 input
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
// 2.1) 写法一 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
timeout = setTimeout(() => {
// 2.2)写法二 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
// timeout = setTimeout(function() {
console.log('---2this', this, arg) // 3)
fn.apply(this, [arg]); // 4.1) 写法二
// fn(arg) // 4.2) 写法一
}, 500);
};
}
function sayHi(arg) {
console.log('---3this防抖成功', this, arg); // 5)
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, {a:'aaaa'})); // 防抖
// 解析:
// 1) this指向 input --- 因为debounce的返回值其实就是 inp.addEventListener('input')的回调函数,事件处理函数中的 this指向 DOM
// 2.1)会导致 3)this指向 input --- 因为箭头函数的this指向箭头函数定义时上层作用域中的this,而不是使用时所在的对象
// 2.2) 会导致 3)this指向 window --- 因为普通函数执行时 setTimout中的 this指向window --
// 原因:把setTimout中的回调看成单独的fn,未做绑定,看成在window下执行
// 4.1) 结合 2.1)会导致 5) 指向 input --- 因为 2.1)执行后,导致 3)this指向input, 此时this被 apply绑定了,5) 指向input
// 4.1) 结合 2.2)会导致 5) 指向 window --- 因为 this 在2.2)就指向了 window,apply绑定的是 window
// 4.2) 不论结合 2.1)还是 2.2)都会导致 5) 指向 window --- 因为未做任何绑定,默认this指向window
1 当使用了箭头函数 和 apply
- 2.1)会导致 3)this指向 input --- 因为箭头函数的this指向箭头函数定义时上层作用域中的this,而不是使用时所在的对象
- 4.1) 结合 2.1)会导致 5) 指向 input --- 因为 2.1)执行后,导致 3)this指向input, 此时this被 apply绑定了,5) 指向input
小结一下:
- 使用箭头函数:为了箭头函数体里的this指向input; 使用普通函数this指向window了
- 使用apply:加上 apply 确保 在 sayHi 函数里的 this 指向的是 input对象(不然就指向 window 了,不是我们想要的);
- 上行补充 ~~~ 因为sayHi函数是在全局中调用运行,所以this指向了window,所以才需要加上apply,显示绑定this值(input对象)到sayHi函数里面去
function debounce(fn, arg) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
console.log('---1 this', this) //1) this 指向 input
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 2.1)
console.log('---', this, arg) // 3) this指向 input
fn.apply(this, [arg]); // 4.1) this与上行this一致,此时 this指向 input
}, 500);
};
}
function sayHi(arg) {
console.log('防抖成功', this, arg); // 5) this 指向 input
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, {a:'aaaa'})); // 防抖
2 当使用了普通函数 和 apply
- 2.2) 会导致 3)this指向 window --- 因为普通函数执行时 setTimout中的 this指向window -- 原因:把setTimout中的回调看成单独的fn,未做绑定,看成在window下执行
- 4.1) 结合 2.2)会导致 5) 指向 window --- 因为 this 在2.2)就指向了 window,apply传的this就是 window
function debounce(fn, arg) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
console.log('---1this', this) //1) 3) this指向 input
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(function() { // 2.2)
console.log('---2this', this, arg) // 3) this指向 window
fn.apply(this, [arg]); // 4.1) this与上行中的this一样,指向 window
}, 500);
};
}
function sayHi(arg) {
console.log('---3this防抖成功', this, arg); // 5) this指向 window
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, {a:'aaaa'})); // 防抖
3 当不使用 apply
-
- this 指向 window;因为 4.2)未绑定this
- 不论结合 2.1)还是 2.2),影响的只是setTimout里的this,即3)
function debounce(fn, arg) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
console.log('---1this', this) //1) this指向 input
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 2.1)
// timeout = setTimeout(function() { // 2.2)
console.log('---2this', this, arg) // 3) 2.1)执行时 this指向 input;2.2)执行时 this指向 window
fn(arg) // 4.2) 该行未绑定this
}, 500);
};
}
function sayHi(arg) {
// 5) this 指向 window, 因为 4.2)未绑定this, 不论结合 2.1)还是 2.2),该行this都为window
console.log('---3this防抖成功', this, arg);
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi, {a:'aaaa'})); // 防抖