Hàm debounce (Chống rung)
Hàm debounce giúp trì hoãn việc thực thi một hàm cho đến khi không có sự kiện nào được kích hoạt trong một khoảng thời gian nhất định.
1. Hiểu đơn giản về quá trình:
- Khi sự kiện được kích hoạt, hàm tương ứng sẽ không được thực thi ngay lập tức, mà sẽ chờ đợi một khoảng thời gian.
- Nếu sự kiện được kích hoạt liên tục, thời gian chờ sẽ được reset lại.
- Chỉ khi không có sự kiện nào được kích hoạt trong khoảng thời gian chờ, hàm mới được thực thi.
2. Ứng dụng của debounce:
- Nhập liệu vào ô input để tìm kiếm hoặc gửi thông tin.
- Nhấn nút liên tục để kích hoạt sự kiện.
- Theo dõi sự kiện cuộn trang để thực hiện các thao tác cụ thể.
- Sự kiện resize của trình duyệt.
3. Sử dụng thư viện thứ ba để thực hiện debounce
Có nhiều thư viện hỗ trợ debounce như lodash và underscore. Trong ví dụ này, chúng ta sẽ sử dụng underscore. Bạn có thể cài đặt underscore bằng cách tải về và thêm vào dự án, thông qua CDN, hoặc sử dụng npm.
4. Ví dụ đơn giản:
Để thực hiện debounce trong ô nhập liệu, bạn có thể sử dụng đoạn mã sau:
<script>
const inputEl = document.querySelector("input");
let counter = 0;
const inputChange = function(event) {
console.log(`Gửi yêu cầu mạng lần thứ ${++counter}`, this, event);
};
// Đặt debounce với thời gian 2 giây
inputEl.oninput = _.debounce(inputChange, 2000);
</script>
5. Viết hàm debounce từ đầu:
a. Cài đặt cơ bản:
function debounce(fn, delay) {
let timer = null;
const _debounce = function() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, delay);
};
return _debounce;
}
b. Xử lý tham số và thay đổi this:
function debounce(fn, delay) {
let timer = null;
const _debounce = function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
return _debounce;
}
c. Thêm tính năng thực thi ngay lập tức:
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = false;
const _debounce = function(...args) {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
fn.apply(this, args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false;
timer = null;
}, delay);
}
};
_debounce.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
isInvoke = false;
};
return _debounce;
}
d. Thêm tính năng hủy bỏ:
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = false;
const _debounce = function(...args) {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
fn.apply(this, args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false;
timer = null;
}, delay);
}
};
_debounce.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
isInvoke = false;
};
return _debounce;
}
e. Xử lý giá trị trả về:
function debounce(fn, delay, immediate = false, resultCallback) {
let timer = null;
let isInvoke = false;
const _debounce = function(...args) {
return new Promise((resolve, reject) => {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
const result = fn.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
isInvoke = true;
} else {
timer = setTimeout(() => {
const result = fn.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
isInvoke = false;
timer = null;
}, delay);
}
});
};
_debounce.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
isInvoke = false;
};
return _debounce;
}
Hàm throttle (Giới hạn tần suất)
Hàm throttle giúp giảm tần suất thực thi của một hàm, đảm bảo rằng nó chỉ được thực thi theo một tần suất cố định.
1. Hiểu đơn giản về quá trình:
- Khi sự kiện được kích hoạt, hàm tương ứng sẽ được thực thi.
- Nếu sự kiện được kích hoạt liên tục, hàm sẽ chỉ được thực thi theo tần suất đã định.
- Tần suất thực thi là cố định, không phụ thuộc vào số lần sự kiện được kích hoạt.
2. Ứng dụng của throttle:
- Theo dõi sự kiện cuộn trang.
- Theo dõi sự kiện di chuyển chuột.
- Nhấn nút liên tục để kích hoạt sự kiện.
- Một số thiết kế trong game.
3. Sử dụng thư viện thứ ba để thực hiện throttle
Các thư viện như lodash và underscore cũng hỗ trợ throttle. Dưới đây là một ví dụ sử dụng underscore:
4. Ví dụ đơn giản:
<script>
const inputEl = document.querySelector("input");
let counter = 0;
const inputChange = function(event) {
console.log(`Gửi yêu cầu mạng lần thứ ${++counter}`, this, event);
};
// Đặt throttle với thời gian 2 giây
inputEl.oninput = _.throttle(inputChange, 2000);
</script>
5. Viết hàm throttle từ đầu:
a. Cài đặt cơ bản:
function throttle(fn, interval) {
let lastTime = 0;
const _throttle = function() {
const nowTime = new Date().getTime();
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
fn();
lastTime = nowTime;
}
};
return _throttle;
}
b. Thêm tùy chọn thực thi ở đầu và cuối:
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing } = options;
let lastTime = 0;
let timer = null;
const _throttle = function() {
const nowTime = new Date().getTime();
if (!lastTime && !leading) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn();
lastTime = nowTime;
return;
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading ? 0 : new Date().getTime();
fn();
}, remainTime);
}
};
return _throttle;
}
c. Xử lý tham số và thay đổi this:
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing } = options;
let lastTime = 0;
let timer = null;
const _throttle = function(...args) {
const nowTime = new Date().getTime();
if (!lastTime && !leading) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
lastTime = nowTime;
return;
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading ? 0 : new Date().getTime();
fn.apply(this, args);
}, remainTime);
}
};
return _throttle;
}
d. Thêm tính năng hủy bỏ:
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing } = options;
let lastTime = 0;
let timer = null;
const _throttle = function(...args) {
const nowTime = new Date().getTime();
if (!lastTime && !leading) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
lastTime = nowTime;
return;
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading ? 0 : new Date().getTime();
fn.apply(this, args);
}, remainTime);
}
};
_throttle.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
lastTime = 0;
};
return _throttle;
}
e. Xử lý giá trị trả về:
function throttle(fn, interval, options = { leading: true, trailing: false, resultCallback: null }) {
const { leading, trailing, resultCallback } = options;
let lastTime = 0;
let timer = null;
const _throttle = function(...args) {
return new Promise((resolve, reject) => {
const nowTime = new Date().getTime();
if (!lastTime && !leading) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
const result = fn.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
lastTime = nowTime;
return;
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading ? 0 : new Date().getTime();
const result = fn.apply(this, args);
if (resultCallback) resultCallback(result);
resolve(result);
}, remainTime);
}
});
};
_throttle.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
lastTime = 0;
};
return _throttle;
}