目录
一、防抖 debounce
1. 概念
- 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间
- 当事件密集触发时,函数的触发会被频繁的推迟,并把上一次的给取消掉
- 只有等待了一段时间也没有事件触发,才会真正的执行响应函数
2. 应用场景
- 输入框中频繁的输入内容,搜索或者提交信息
- 频繁的点击按钮,触发某个事件
- 监听浏览器滚动事件,完成某些特定操作
- 用户缩放浏览器的resize事件
3. 使用 underscore 实现防抖
01 - 代码
<body>
<input type="text" />
<!-- 1. CDN引入: 网络的js文件 -->
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>
<script>
// 2.获取input元素
const inputEl = document.querySelector('input');
// 3.防抖处理代码
let counter = 1;
inputEl.oninput = _.debounce(function () {
console.log(`发送网络请求${counter++}:`, this.value);
}, 1000);
</script>
</body>
02 - 效果
4. 实现
01 - 基本实现
代码
<body>
<input type="text" />
<script>
function starDebounce(fn, delay) {
// 1.用于记录上一次事件触发的timer
let timer = null;
// 2. 返回新的函数
return function () {
// 3. 如果有再次触发(更多次触发)事件, 那么取消上一次的事件
if (timer) clearTimeout(timer);
// 4. 绑定当前事件
timer = setTimeout(() => {
// 5. 延迟后执行
fn();
// 6. 执行后把当前定时器删除
timer = null;
}, delay);
};
}
</script>
<script>
const inputDom = document.querySelector('input');
let counter = 1;
inputDom.oninput = starDebounce(function () {
console.log(`发送网络请求${counter++}`);
}, 1000);
</script>
</body>
效果
02 - 优化 => this 和 参数绑定
代码
<body>
<input type="text" />
<script>
function starDebounce(fn, delay) {
let timer = null;
// 1. 返回新的函数,此时这个函数中的this为绑定的dom对象 => 相当于 inputDom的oninput指向这个函数
// 2. 接受参数
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
// 3. 绑定this,同时把参数传递给fn
fn.apply(this, args);
timer = null;
}, delay);
};
}
</script>
<script>
const inputDom = document.querySelector('input');
let counter = 1;
inputDom.oninput = starDebounce(function (e) {
console.log(`发送网络请求 :${counter++} => ${this.value} `, e);
}, 1000);
</script>
</body>
效果
03 - 优化 => 取消功能
代码
<body>
<input type="text" />
<button>取消</button>
<script>
function starDebounce(fn, delay) {
let timer = null;
// 1. 返回新的函数,此时这个函数中的this为绑定的dom对象 => 相当于 inputDom的oninput指向这个函数
// 2. 接受参数
const _debounce = function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
// 3. 绑定this,同时把参数传递给fn
fn.apply(this, args);
timer = null;
}, delay);
};
// 3. 因为函数也是一个对象,所以
_debounce.cancle = function () {
if (timer) clearTimeout(timer);
timer = null;
};
// 4. 返回函数
return _debounce;
}
</script>
<script>
const inputDom = document.querySelector('input');
let counter = 1;
const debounceFn = starDebounce(function (e) {
console.log(`发送网络请求 :${counter++} => ${this.value} `, e);
}, 1000);
// 执行
inputDom.oninput = debounceFn;
// 取消
const btnDom = document.querySelector('button');
btnDom.onclick = debounceFn.cancle;
</script>
</body>
效果
04 - 优化 => 立即执行功能
代码
<body>
<input type="text" />
<button>取消</button>
<script>
function starDebounce(fn, delay, immediate = false) {
let timer = null;
// 1. 是否是第一次执行
let isInvoke = true;
const _debounce = function (...args) {
if (timer) clearTimeout(timer);
// 2. 第一次操作不需要延迟
if (immediate && isInvoke) {
fn.apply(this, args);
timer = null;
isInvoke = false;
return;
}
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
// 3. 执行完后恢复
isInvoke = true;
}, delay);
};
_debounce.cancle = function () {
if (timer) clearTimeout(timer);
timer = null;
// 3. 执行完后恢复
isInvoke = true;
};
return _debounce;
}
</script>
<script>
const inputDom = document.querySelector('input');
let counter = 1;
const debounceFn = starDebounce(
function (e) {
console.log(`发送网络请求 :${counter++} => ${this.value} `, e);
},
1000,
true
);
// 执行
inputDom.oninput = debounceFn;
// 取消
const btnDom = document.querySelector('button');
btnDom.onclick = debounceFn.cancle;
</script>
</body>
效果
05 - 优化 => 获取返回值
代码
<body>
<input type="text" />
<button>取消</button>
<script>
function starDebounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = true;
const _debounce = function (...args) {
// 1. 因为有延迟,使用promise
return new Promise((resolve, reject) => {
try {
let res = null;
if (timer) clearTimeout(timer);
// 2. 第一次操作不需要延迟
if (immediate && isInvoke) {
// 2. 接受函数的返回值
res = fn.apply(this, args);
// 3. 传递出去
resolve(res);
timer = null;
isInvoke = false;
return;
}
timer = setTimeout(() => {
res = fn.apply(this, args);
// 3. 传递出去
resolve(res);
timer = null;
// 3. 执行完后恢复
isInvoke = true;
}, delay);
} catch (error) {
reject(error);
}
});
};
_debounce.cancle = function () {
if (timer) clearTimeout(timer);
timer = null;
// 3. 执行完后恢复
isInvoke = true;
};
return _debounce;
}
</script>
<script>
const debounceFn = starDebounce(function (name, text, age) {
console.log(name, text, age);
return '执行完了';
}, 1000);
// 4. 传递参数并接受返回值
debounceFn('coder', 'star', 18).then((res) => {
console.log(res);
});
// 取消
const btnDom = document.querySelector('button');
btnDom.onclick = debounceFn.cancle;
</script>
</body>
06 - 最终版本
function starDebounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = true;
const _debounce = function (...args) {
return new Promise((resolve, reject) => {
try {
let res = null;
if (timer) clearTimeout(timer);
if (immediate && isInvoke) {
res = fn.apply(this, args);
resolve(res);
timer = null;
isInvoke = false;
return;
}
timer = setTimeout(() => {
res = fn.apply(this, args);
resolve(res);
timer = null;
isInvoke = true;
}, delay);
} catch (error) {
reject(error);
}
});
};
_debounce.cancle = function () {
if (timer) clearTimeout(timer);
timer = null;
isInvoke = true;
};
return _debounce;
}
二、节流 throttle
1. 概念
- 当事件触发时,会执行这个事件的响应函数
- 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数
- 不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的
2. 应用场景
- 监听页面的滚动事件
- 鼠标移动事件
- 用户频繁点击按钮操作
- 轮播图的按钮滚动
3. 使用 underscore 实现节流
01 - 代码
<body>
<input type="text" />
<!-- 1. CDN引入: 网络的js文件 -->
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>
<script>
// 2.获取input元素
const inputEl = document.querySelector('input');
// 3.节流处理代码
let counter = 1;
inputEl.oninput = _.throttle(function () {
console.log(`发送网络请求${counter++}:`, this.value);
}, 1000);
</script>
</body>
02 - 效果
4. 实现
01 - 基本实现
代码
<body>
<input type="text" />
<script>
/**
* fn : 传入的函数
* interval : 间隔的请求时间
* 公式 : 等待时间 = 间隔时间 - ( 当前时间 - 开始时间 ) => interval - ( nowTime - startTime )
*
* 等待时间 <= 0 即可执行函数
*/
function starThrottle(fn, interval) {
// 1. 获取开始时间,赋初始值为0
let startTime = 0;
function throttle() {
// 2. 获取当前时间
const nowTime = new Date().getTime();
// 3. 计算等待时间
const waitTime = interval - (nowTime - startTime);
// 4. 判断是否执行函数,第一次会默认执行
if (waitTime <= 0) {
fn();
// 5.一旦执行完后,把当前时间赋值给开始时间
startTime = nowTime;
}
}
return throttle;
}
</script>
<script>
// 2.获取input元素
const inputEl = document.querySelector('input');
// 3.节流处理代码
let counter = 1;
inputEl.oninput = starThrottle(function () {
console.log(`发送网络请求${counter++}:`, this.value);
}, 2000);
</script>
</body>
效果
02 - 优化 => this 和 参数的绑定
代码
<body>
<input type="text" />
<script>
function starThrottle(fn, interval) {
let startTime = 0;
// 节流函数接受参数
function throttle(...args) {
const nowTime = new Date().getTime();
const waitTime = interval - (nowTime - startTime);
if (waitTime <= 0) {
// 相当于 inputEl 直接调用该函数,所以this指向 inputEl
// 把参数传给函数,即可在回调函数中拿到
fn.apply(this, args);
startTime = nowTime;
}
}
return throttle;
}
</script>
<script>
// 2.获取input元素
const inputEl = document.querySelector('input');
// 3.节流处理代码
let counter = 1;
inputEl.oninput = starThrottle(function (e) {
// 使用
console.log(`发送网络请求${counter++}:`, this.value, e);
}, 2000);
</script>
</body>
效果
03 - 优化 => 立即执行功能
代码
<body>
<input type="text" />
<script>
/**
* fn : 传入的函数
* interval : 间隔时间
* immediate : 第一次是否执行,默认是马上执行的
*/
function starThrottle(fn, interval, immediate = true) {
let startTime = 0;
function throttle(...args) {
const nowTime = new Date().getTime();
// 如果immediate为false,且 开始时间为0时
// 把当前时间赋值给开始时间,这样使得第一次不会执行
if (!immediate && startTime === 0) {
startTime = nowTime;
}
const waitTime = interval - (nowTime - startTime);
if (waitTime <= 0) {
fn.apply(this, args);
startTime = nowTime;
}
}
return throttle;
}
</script>
<script>
// 2.获取input元素
const inputEl = document.querySelector('input');
// 3.节流处理代码
let counter = 1;
inputEl.oninput = starThrottle(
function (e) {
// 使用
console.log(`发送网络请求${counter++}:`, this.value, e);
},
1000,
false
);
</script>
</body>
效果
04 - 优化 => 获取返回值
代码
<script>
function starThrottle(fn, interval, immediate = true) {
let startTime = 0;
function throttle(...args) {
return new Promise((resolve, reject) => {
try {
const nowTime = new Date().getTime();
// 如果immediate为false,且 开始时间为0时
// 把当前时间赋值给开始时间,这样使得第一次不会执行
if (!immediate && startTime === 0) {
startTime = nowTime;
}
const waitTime = interval - (nowTime - startTime);
if (waitTime <= 0) {
//
const res = fn.apply(this, args);
resolve(res);
startTime = nowTime;
}
} catch (error) {
reject(error);
}
});
}
return throttle;
}
</script>
<script>
// 3.节流处理代码
let counter = 1;
const throttleFn = starThrottle(function (...e) {
// 使用
console.log(`发送网络请求${counter++}:`, e);
return '无敌 🦖 战神';
}, 1000);
throttleFn('inside', 'args').then((res) => {
console.log('返回值:', res);
});
</script>
05 - 最终版本
function starThrottle(fn, interval, immediate = true) {
let startTime = 0;
function throttle(...args) {
return new Promise((resolve, reject) => {
try {
const nowTime = new Date().getTime();
if (!immediate && startTime === 0) {
startTime = nowTime;
}
const waitTime = interval - (nowTime - startTime);
if (waitTime <= 0) {
//
const res = fn.apply(this, args);
resolve(res);
startTime = nowTime;
}
} catch (error) {
reject(error);
}
});
}
return throttle;
}
5. 第二种方法实现节流
// 直接最终版本
<script>
/**
* fn : 传入的函数
* interval : 间隔时间
* 不方便设置第一次马上执行
*/
function starThrottle(fn, interval) {
// 1. 是否锁住
let locked = false;
function throttle(...args) {
return new Promise((resolve, reject) => {
try {
// 2. 判断是否锁住,如果锁住直接返回
if (locked) return true;
// 3. 进来后直接锁住
locked = true;
// 4. 开启定时器
setTimeout(() => {
const res = fn.apply(this, args);
resolve(res);
// 5. 执行完后,解锁
locked = false;
}, interval);
} catch (error) {
reject(error);
}
});
}
return throttle;
}
</script>