前端有很多性能优化的方式,面对用户与网页的频繁交互,如输入框打字、按钮点击、滚动事件等,我们如何确保应用的响应既迅速又高效?
大家应该都简单了解防抖和节流本质上就是优化这种高频率执行代码的手段,那么他们之间有什么区别呢?应该如何正确根据具体的场景来选择使用呢?
确保在指定的时间间隔内,无论连续触发了多少次事件,只有最后一次事件会在该间隔结束后执行。(触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。)
核心逻辑:
举例:
想象一台自动售货机,里面都是不同价格的商品,当你投入相应的硬币,会自动选择对应商品。但是为了避免还没投完就出商品,售货机设计不是投完立即掉出商品,而是有一个短暂的延迟,在这个延迟期间,如果你再次投入硬币,售货机重新进入延迟,只有当延迟期过后,之前投入硬币才会被处理。这样可以避免因为误操作或快速连续操作导致的错误购买。
确保在指定的时间间隔内,无论触发了多少次事件,只有第一次事件会被执行,后续事件在这个间隔内都不会执行。(连续触发事件但是在 n 秒中只执行第一次触发函数)
核心逻辑:
举例:
想象一个繁忙的十字路口,交通信号灯每60秒变换一次。不论有多少车辆通过,信号灯都不会更快地变换。这就像节流,无论事件触发的频率多高,每个周期内只执行一次。
// 创建一个防抖函数,它返回一个新的函数,该新函数在指定的 wait 时间后执行 func
function debounce(func, wait) {
// 保存定时器的引用
let timeout;
// 返回的函数是用户实际调用的函数,它包含了防抖逻辑
return function(...args) {
// 保存当前的 this 上下文
const context = this;
console.log(context);
// 清除之前的定时器,如果存在
if (timeout) clearTimeout(timeout);
// 设置一个新的定时器
// 当指定的 wait 时间过后,将执行 func 函数
// 并将当前的 this 上下文和参数传入
timeout = setTimeout(function() {
// 执行原始函数,绑定正确的 this 上下文和参数
func.apply(context, args);
}, wait);
};
}
timeout
(即是否有一个定时器在运行)。func
,此时使用clearTimeout
清除之前的定时器。timeout
,如果在wait
指定的时间内再次触发防抖函数,之前的定时器会被清除并重新设置,这意味着func
的执行会被不断推迟。wait
内没有再次触发防抖函数时,timeout
才会到达,此时会执行原始函数func
,并且使用apply
方法将存储的context
和args
传递给它。function throttle(func, limit) {
let inThrottle = false;
return function(...args) {
const context = this; // 保存当前的 this 上下文
if (!inThrottle) {
// 执行传入的函数
func.apply(context, args);
inThrottle = true; // 标记为正在节流
// 使用闭包和 setTimeout 来在指定的延迟后重置 inThrottle
setTimeout(() => {
inThrottle = false; // 重置节流状态
}, limit);
}
};
}
func
:需要被节流的函数。limit
:表示在指定的时间间隔后,func
才能再次被执行的时间(以毫秒为单位)。inThrottle
:一个布尔值,用来标记func
是否处于可执行状态。context
:保存当前的this
上下文,确保在执行func
时this
指向正确。args
:使用扩展运算符...
来收集所有参数,以便将它们传递给func
。setTimeout
:在指定的limit
时间后执行,将inThrottle
重置为false
,这样func
就可以在下一次调用时被执行了。搜索框输入:当用户在搜索框中输入文本时,通常会有一些实时搜索建议。使用防抖可以确保只有在用户停止输入一段时间后才触发搜索请求,避免因为快速连续输入而导致的大量请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Debounced Search</title>
</head>
<body>
<input type="text" id="searchBox" placeholder="输入内容...">
<ul id="suggestionsList"></ul>
<script>
// 防抖函数
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
// 搜索建议函数(示例)
function showSuggestions(query) {
const suggestionsList = document.getElementById('suggestionsList');
// 这里使用 query 参数来模拟搜索建议逻辑
suggestionsList.innerHTML = `<b>搜索: ${query}</b>`;
console.log(`Searching for: ${query}`);
}
// 获取搜索框元素和其 oninput 事件
const searchBox = document.getElementById('searchBox');
// 使用防抖包装搜索建议函数,设置防抖时间为 1500 毫秒
const debouncedShowSuggestions = debounce(showSuggestions, 1500);
// 绑定事件处理函数
searchBox.addEventListener('input', function(event) {
// 使用 event.target.value 获取输入框的值
const query = event.target.value;
debouncedShowSuggestions(query);
});
</script>
</body>
</html>
当用户在搜索框中输入文本时,会触发 input
事件。我们绑定了一个防抖后的 showSuggestions
函数到这个事件上,这样当用户停止输入1500毫秒后,showSuggestions
函数才会执行,以此来模拟获取搜索建议的过程。
滚动事件:在处理滚动事件时,如无限滚动加载更多内容,节流可以限制触发事件处理程序的频率,避免过度触发导致性能问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Throttled Infinite Scroll</title>
<style>
#contentList {
list-style: none;
padding: 0;
overflow-y: auto;
height: 300px; /* 根据需要调整这个高度 */
background: #f9f9f9;
}
#contentList li {
padding: 10px;
border-bottom: 1px solid #ddd;
background-color: white;
}
</style>
</head>
<body>
<ul id="contentList">
<!-- 初始内容列表项 -->
<li>Item 1</li>
<li>Item 2</li>
<!-- 更多内容根据滚动加载 -->
</ul>
<script>
// 节流函数
function throttle(func, limit) {
let inProgress = false;
return function() {
if (!inProgress) {
inProgress = true;
setTimeout(() => {
func.apply(this, arguments);
inProgress = false;
}, limit);
}
};
}
// 加载更多内容的函数
function loadMoreContent() {
const contentList = document.getElementById('contentList');
// 检查是否滚动到距离底部 100 像素以内
if (contentList.scrollTop + contentList.clientHeight + 100 >= contentList.scrollHeight) {
console.log('加载更多内容...');
// 这里模拟从服务器加载数据,实际上你应该发起 AJAX 请求
setTimeout(() => {
for (let i = 0; i < 5; i++) { // 每次加载 5 个新项
const newItem = document.createElement('li');
newItem.textContent = 'Item ' + (contentList.children.length + 1);
contentList.appendChild(newItem);
}
}, 1000);
}
}
// 获取滚动容器
const contentListElement = document.getElementById('contentList');
// 给滚动事件添加节流
contentListElement.addEventListener('scroll', throttle(loadMoreContent, 1000));
// 填充初始内容以确保可以滚动
for (let i =2; i < 10; i++) {
const listItem = document.createElement('li');
listItem.textContent = 'Item ' + (i + 1);
contentListElement.appendChild(listItem);
}
</script>
</body>
</html>
setTimeout
函数实现,利用时间延迟来控制回调函数的执行。上下文通常指的是this
所指向的对象。在不同的函数调用方式中,this
的指向可能不同:
this
指向全局对象(在浏览器中是window
)。this
指向该对象。this
指向新创建的实例。this
通常指向触发事件的DOM元素。const person = {
name: 'John',
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
};
person.greet(); // 输出: Hello, my name is John.
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this); // 在这里,this 指向 button 元素
});
this
用于引用正在创建的新实例。function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // 输出: John
this
可以用来访问外部的全局上下文或另一个对象。const myModule = {
name: 'My Module',
logName: function() {
console.log(this.name);
}
};
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。