彻底理清防抖和节流(前端性能优化)

引言:

前端有很多性能优化的方式,面对用户与网页的频繁交互,如输入框打字、按钮点击、滚动事件等,我们如何确保应用的响应既迅速又高效?

大家应该都简单了解防抖和节流本质上就是优化这种高频率执行代码的手段,那么他们之间有什么区别呢?应该如何正确根据具体的场景来选择使用呢?

1.定义

防抖(Debounce)
确保在指定的时间间隔内,无论连续触发了多少次事件,只有最后一次事件会在该间隔结束后执行。(触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。)

核心逻辑

  • 重置计时器:每次事件触发时,都会重置计时器。
  • 执行时机:只有在用户停止触发事件指定时间间隔后,才会执行最后一次事件。

举例:

想象一台自动售货机,里面都是不同价格的商品,当你投入相应的硬币,会自动选择对应商品。但是为了避免还没投完就出商品,售货机设计不是投完立即掉出商品,而是有一个短暂的延迟,在这个延迟期间,如果你再次投入硬币,售货机重新进入延迟,只有当延迟期过后,之前投入硬币才会被处理。这样可以避免因为误操作或快速连续操作导致的错误购买。

节流(Throttle)
确保在指定的时间间隔内,无论触发了多少次事件,只有第一次事件会被执行,后续事件在这个间隔内都不会执行。(连续触发事件但是在 n 秒中只执行第一次触发函数)

核心逻辑

  • 单次执行:在时间间隔内只执行一次事件处理函数。
  • 忽略后续触发:在时间间隔内,后续的事件触发将被忽略。

举例:

想象一个繁忙的十字路口,交通信号灯每60秒变换一次。不论有多少车辆通过,信号灯都不会更快地变换。这就像节流,无论事件触发的频率多高,每个周期内只执行一次

2.实现方式/原理
防抖:
// 创建一个防抖函数,它返回一个新的函数,该新函数在指定的 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就可以在下一次调用时被执行了。
 3.应用场景
防抖(Debounce):

搜索框输入:当用户在搜索框中输入文本时,通常会有一些实时搜索建议。使用防抖可以确保只有在用户停止输入一段时间后才触发搜索请求,避免因为快速连续输入而导致的大量请求。

<!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 函数才会执行,以此来模拟获取搜索建议的过程。

节流(Throttle):

滚动事件:在处理滚动事件时,如无限滚动加载更多内容,节流可以限制触发事件处理程序的频率,避免过度触发导致性能问题

<!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>

4.两者总结
相同点:
  • 实现机制:两者都可以通过JavaScript的setTimeout函数实现,利用时间延迟来控制回调函数的执行。
  • 性能优化:它们的共同目的都是减少函数的执行频率,以此来提高程序的性能,避免不必要的计算资源消耗
不同点:

1 执行时机

  • 防抖(Debounce):确保在指定的时间间隔结束后执行一次函数。如果在这段时间内多次触发事件,则只有最后一次事件会在延迟后执行函数。
  • 节流(Throttle):确保在指定的时间间隔内最多执行一次函数。无论在这段时间内触发了多少次事件,只有第一次事件会立即执行函数。

2 应用场景

  • 防抖:适用于搜索框输入、表单验证等场景,用户完成输入后,才执行相关操作。
  • 节流:适用于滚动事件、按钮点击等,需要在连续事件中合理控制执行频率的场景。

3 触发逻辑

  • 防抖:关注一段时间内的连续触发,但只对最后一次操作做出响应。
  • 节流:在一段时间内,无论触发多少次事件,只响应一次。

4 分辨技巧

  • 如果您希望在一系列快速操作结束后只执行一次函数,那么使用防抖
  • 如果您希望在一系列快速操作中合理控制函数的执行频率,那么使用节流
补充:

上下文(Context)指的是什么?

上下文通常指的是this所指向的对象。在不同的函数调用方式中,this的指向可能不同:

  1. 全局上下文:在全局作用域中,this指向全局对象(在浏览器中是window)。
  2. 对象方法上下文:当一个函数作为对象的方法被调用时,this指向该对象。
  3. 构造函数上下文:在构造函数中,this指向新创建的实例。
  4. 事件处理上下文:在事件处理函数中,this通常指向触发事件的DOM元素。
什么时候使用上下文?
  1. 对象的方法:当您调用一个对象的方法,并且需要在该方法内部引用该对象的其他属性或方法时。

const person = {
  name: 'John',
  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};
person.greet(); // 输出: Hello, my name is John.

2 事件处理器:在事件驱动的编程中,如DOM事件处理,您可能需要在事件处理函数中引用绑定事件的元素。

const button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this); // 在这里,this 指向 button 元素
});

3 构造函数:在使用构造函数创建实例时,this用于引用正在创建的新实例

function Person(name) {
  this.name = name;
}
const john = new Person('John');
console.log(john.name); // 输出: John

4 模块或命名空间:在模块模式或命名空间中,this可以用来访问外部的全局上下文或另一个对象。

const myModule = {
  name: 'My Module',
  logName: function() {
    console.log(this.name);
  }
};

                        
原文链接:https://blog.youkuaiyun.com/qq_62799214/article/details/138204516

<think>好的,我现在需要帮助用户了解SQL语句的编写查询性能优化技巧。首先,我得理清用户的需求。用户提到了参考内容中的引用,其中引用[1][4]涉及性能优化基础SQL编写,引用[2][3]则关于编写技巧批量数据处理。用户希望综合这些信息,得到结构清晰的回答,包括编写技巧优化方法,同时生成相关问题。 首先,我应该从SQL语句的编写技巧开始。根据引用[2],关键字函数的使用很重要,比如SELECT、WHERE这些关键字,以及聚合函数如COUNT、SUM。需要强调关键字的正确使用,避免保留字作为标识符。另外,引用[4]中的三表联接示例,可以说明JOIN的用法WHERE条件的位置。这里需要注意JOIN的顺序类型,比如LEFT JOININNER JOIN的区别,以及WHERE条件对查询结果的影响。 接下来是性能优化部分。引用[1]提到性能优化是个大难题,尤其是在数据量大逻辑复杂的情况下。索引的使用是关键,比如在WHEREJOIN的列上创建索引。但索引过多会影响写入性能,需要平衡。此外,避免使用SELECT *,而是指定需要的列,减少数据传输量。引用[3]中的批量处理数据,可能涉及到循环变量的使用,但需要指出避免在SQL中使用游标循环,转而使用集合操作,比如UPDATE ... WHERE IN,提高效率。 然后要考虑分步骤说明。比如,编写技巧部分可以分为关键字使用、函数应用、JOIN操作、子查询优化等。优化技巧则分为索引、查询简化、执行计划分析、分页优化等。每个点都需要结合引用内容,并自然地添加引用标识。 还需要检查是否有遗漏的重要点。例如,执行计划分析在优化中非常重要,用户可能需要了解如何使用EXPLAIN或SQL Server的执行计划工具。此外,事务的使用锁的机制也可能影响性能,需要简要提及。 最后,生成相关问题。根据用户提供的示例,问题应围绕SQL编写优化展开,比如如何选择索引、避免全表扫描的方法、执行计划的使用、分页优化技巧等。确保问题覆盖主要知识点,帮助用户进一步学习。 现在需要组织语言,确保结构清晰,使用中文,正确应用LaTeX格式,如独立公式用$$...$$,行内用$...$。同时,引用标识要放在相关内容的末尾,如优化索引部分引用[1],批量处理引用[3]等。 可能需要注意的地方是,避免技术术语过于晦涩,保持解释的易懂性。例如,解释索引为什么能提高查询速度,但会增加写入开销。还有,在讨论执行计划时,说明如何通过它来识别瓶颈。 总结下来,回答的结构应该是: 1. SQL语句编写技巧 a. 关键字与函数 b. JOIN操作 c. 子查询优化 d. 批量处理 2. 查询性能优化技巧 a. 索引优化 b. 简化查询 c. 分析执行计划 d. 分页优化 e. 事务与锁 每个部分结合引用内容,最后生成相关问题。</think>### SQL语句编写与性能优化技巧 #### 一、SQL语句编写技巧 1. **关键字与函数的正确使用** - SQL关键字(如$SELECT$、$WHERE$、$JOIN$)是语法核心,需按规范使用。例如,避免将关键字作为表名或列名,否则需用方括号转义,如$SELECT [group] FROM table$[^2]。 - 函数(如聚合函数$COUNT()$、$SUM()$)需结合场景选择。例如,统计行数优先用$COUNT(1)$而非$COUNT(*)$,减少资源消耗。 2. **JOIN操作的优化** - 多表关联时,明确关联条件并控制表数量。例如: ```sql SELECT a.name, b.department FROM employees a LEFT JOIN departments b ON a.dept_id = b.id WHERE a.salary > 10000 ``` 此查询通过$LEFT JOIN$保留主表数据,并通过$WHERE$过滤结果[^4]。 3. **子查询与临时表** - 复杂逻辑可拆分为子查询或公用表达式(CTE),提升可读性。例如: ```sql WITH high_salary_emp AS ( SELECT id FROM employees WHERE salary > 10000 ) SELECT * FROM high_salary_emp ``` 4. **批量数据处理** - 避免逐行操作,使用集合操作提升效率。例如,通过$UPDATE ... WHERE IN$批量更新: ```sql UPDATE orders SET status = 'processed' WHERE id IN (SELECT order_id FROM temp_processed_list) ``` 此方法替代游标循环,减少执行时间[^3]。 --- #### 二、SQL查询性能优化技巧 1. **索引优化** - 为高频查询的$WHERE$、$JOIN$列创建索引,如: ```sql CREATE INDEX idx_employee_dept ON employees(dept_id) ``` 但需注意:索引过多会降低写入速度[^1]。 - 避免对索引列使用函数或运算,否则索引失效。例如,$WHERE YEAR(create_time) = 2023$ 可改为范围查询。 2. **简化查询逻辑** - 减少$SELECT *$,仅获取必要字段。例如,$SELECT id, name$ 比全字段查询减少数据传输量。 - 拆分复杂查询,优先过滤数据再关联。例如,先筛选再$JOIN$。 3. **分析执行计划** - 使用$EXPLAIN$或SQL Server的执行计划工具,识别全表扫描(Table Scan)或索引缺失(Missing Index),针对性优化。 4. **分页查询优化** - 避免$OFFSET$过大时性能下降。改用$WHERE id > {last_id}$方式分页: ```sql SELECT TOP 10 * FROM orders WHERE id > 1000 ORDER BY id ``` 5. **事务与锁机制** - 短事务减少锁竞争,例如批量插入时分批提交: ```sql BEGIN TRANSACTION INSERT INTO logs (...) VALUES (...) COMMIT TRANSACTION ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值