web常识_01.防抖与节流

1. 引入问题

  • 当div 元素绑定了 mousemove 事件每次鼠标移动都会触发方法

  • 如果这时请求后台数据的话,每次鼠标移动都会请求一次后台

  • 造成后台访问量巨大且无效

  • 所以我们要用防抖和节流来解决这个问题

1.1 案例

参考如下代码所存在的问题

当鼠标移动时就会触发方法,如果方法是请求后台方法会造成大量的请求

<!DOCTYPE html>
<html lang**="en"** >
<head>
    <meta charset**="UTF-8"** >
    <title>**Title**</title>
    <style>
        #myid{
            width: 100%;
            height: 200px;
            line-height: 200**px;
            text-align: center;
            color: #fff;
            background-color: #444;
            background-size: 30px;
        }
    </style>
</head>
<body>
<div id="myid" ></div>
<script>
    let count = 0;
    let container= document.querySelector("#myid");
    function dosomething() {
        // 可能会做ajax请求
        containe*.innerHTML = count++
    }
container.onmousemove = dosomething
</script>
</body>
</html>
复制代码

1.2 使用第三方库解决

使用underscore来解决这个问题

<script src="underscore/index.js" ></script>
<script>
let count = 0;
let container = document.querySelector("#myid");
function dosomething() {
    // 可能会做ajax请求
    container.innerHTML = count++
}
// 例用underscore.js实现防抖
container.onmousemove = _.debounce(dosomething,1000)
</script>
复制代码

这样会发现当鼠标停止移动后1秒才出发函数这样就能有效的解决我们的问题

2. 防抖

防抖 :

  • 事件响应函数在一段时间后才执行
  • 如果在这段时间内再次调用,则需重新计算执行事件
  • 当预定的时间内没有再次调用该函数,则执行dosomething

2.1 简单实现防抖

  • 防抖的原理就是让函数定时任务执行
  • 每次调用这个函数都清空所有的定时任务
  • 这样就能保证延时执行最后一次
// 防抖的函数
function debounce(func,wait) {
    // 定时任务变量
    let timeout;
    return function () {
        // 清空所有的定时任务
        clearTimeout(timeout)
        // 定义定时任务
        timeout = setTimeout(func,wait)
    }
}
复制代码

2.2 this指向问题

2.2.1 存在问题

使我我们封装的函数this指向window

function dosomething() {

    // 可能会做ajax请求

    container.innerHTML = count++

    console.log(this); // 简单的防抖函数指向window,第三方库this指向

}
复制代码

image.png

而使用第三方库我们会发现this指向的是该DOM节点

image.png

2.2.2 解决问题

  • 我们通过把this存为一个变量context
  • 在调用定时器的时候让func.apply(context)从而改变this指向
// 防抖的函数
function debounce(func, wait) {
    // 定时任务变量
    let timeout;
    return function () {
        // 改变执行函数内部的this指向
        let context = this;
        // 清空所有的定时任务
        clearTimeout(timeout)
        // 定义定时任务
        timeout = setTimeout(function () {
            func.apply(context);

        },wait)
    }
}
复制代码

2.3 Event的指向问题

2.3.1 存在问题

使我我们封装的函数e指向undifinde

function dosomething(e) {
    // 可能会做ajax请求
    container.innerHTML = count++
    console.log(e);
}
复制代码

image.png

而第三方库效果为

image.png

2.3.2 解决问题

  • 通过arguments获取函数的所有参数并赋值给一个变量args
  • 在定时器中通过func.apply(context,args)添加参数
// 防抖的函数
function debounce(func, wait) {
    // 定时任务变量
    let timeout;
    return function () {
        // 改变event
        let args = arguments;
        // 改变执行函数内部的this指向
        let context = this;
        // 清空所有的定时任务
        clearTimeout(timeout)
        // 定义定时任务
        timeout = setTimeout(function () {
            func.apply(context,args);
        },wait)

    }
}
复制代码

2.4 立即执行

第三方库还有第三个参数可以让函数立即执行

  • 立即执行:比如鼠标移动后马上执行之后1秒内不会再执行,直到下个1秒再立即执行
  • 稍后执行:鼠标移动后不动1秒后才执行

这里我们通过第三个参数immediate

  • 通过immediate参数来写if…else语句
  • 第一次调用timeout没有值
    • timeout没有值
    • callNow = !timeout 为true
    • timeout在wait秒内都有值即在调用该函数不会再执行
    • timeout在wait秒后设置为null,再调用防抖函数也会立即执行
  • 第二次调用当前timeout有值
    • timeout有值
    • callNow = !timeout 为false
    • 过wait秒后设置timeout = null
    • 并且没有执行函数
  • immediate为false时原本逻辑不变

function debounce(func, wait,immediate) {
    var timeout,result;
    let decounced = function () {
        // 改变event
        let args = arguments;
        // 改变执行函数内部的this指向
        let context = this;
        // 清空所有的定时任务
        if(timeout) clearTimeout(timeout);
        // 如果立即执行
        if(immediate){
            // timeout为undifinde所以callNow为true
            let callNow = !timeout;
            timeout = setTimeout(()=>{
                timeout = null;
            },wait);
            // 立即执行
            if(callNow) result = func.apply(context,args);
        }else{
            // 不会立即执行
            timeout = setTimeout(function () {
                func.apply(context,args);
            },wait)
        }
        // 返回这个立即执行的函数
        return result;

    }
    return decounced;
}
复制代码

2.5 取消功能

当我们再1秒内(还没执行完成时)点击按钮可以点击取消执行

首先我们的主函数

let count = 0;
let container = document.querySelector("#myid");
function dosomething(e) {
    // 可能会做ajax请求
    container.innerHTML = count++
    console.log(e);
}

// 防抖函数实例化
let dosome = debounce(dosomething,1000,false);
container.onmousemove = dosome

 // 点击按钮取消方法
function quxiao() {
    dosome.cancel();
}
复制代码

我们需要在debounce函数中实现cancel方法

  • 使用clearTimeout直接清除定时器
  • 把timeout设置为null(为了配合立即执行)
// 取消未完成事件的方法
decounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
}
复制代码

完整代码

function debounce(func, wait,immediate) {
    var timeout,result;
    let decounced = function () {
        // 改变event
        let args = arguments;
        // 改变执行函数内部的this指向
        let context = this;
        // 清空所有的定时任务
        if(timeout) clearTimeout(timeout);
        // 如果立即执行
        if(immediate){
            // timeout为undifinde所以callNow为true
            let callNow = !timeout;
            timeout = setTimeout(()=>{
                timeout = null;
            },wait);
            // 立即执行
            if(callNow) result = func.apply(context,args);
        }else{
            // 不会立即执行
            timeout = setTimeout(function () {
                func.apply(context,args);
            },wait)
        }
        // 返回这个立即执行的函数
        return result;
    }
    // 取消未完成事件的方法
    decounced.cancel = function () {
        clearTimeout(timeout);
        timeout = null;
    }
    return decounced;
}
复制代码

3. 节流

  • 如果持续触发事件,每隔一段时间,只执行一次
  • 所以我们分两种情况来实现
    • 第一次触发,最后不会被调用触发函数
    • 第一次不会触发,最后一次会触发
let count = 0;
let container = document.querySelector("#myid");
let btn = document.querySelector("#btn");
function doSomeThing(e) {
    container.innerHTML = count++
    return "想要的结果"
}
container.onmousemove = _.throttle(doSomeThing,2000,{
    leading:true, // 时间段开始时执行
    trailing:false// 时间段结束时执行

});
复制代码

3.1 第一次触发

使用时间戳实现节流

  • 利用闭包,里面记录一个old时间戳
  • 每次执行时都把新的now时间戳赋值给old
  • now-old > wait 则执行函数
// 第一次触发,最后不会调用触发函数

function throttle(func,wait){
    let context,args;
    //之前的时间戳
    let old = 0;
    return function(){
        context = this;
        args = arguments;
        //获取当前的时间戳
        let now = new Date().valueOf();
        if(now-old > wait){
            // 立即执行
            func.apply(context,args);
            old = now;
        }
    }
}
复制代码

3.2 最后一次触发

和防抖类似,都是使用延时执行

  • 第一次timeout为空所以!timeout为true会立即执行
  • 之后定时器直到wait时间后timeout才为null,才会执行
// 第一次不会触发,最后一次会触发

function throttle(func,wait){
    let context,args,timeout ;
    return function(){
        context = this;
        args = arguments;
        if(!timeout){
            timeout = setTimeout(()=>{
                timeout = null;
                func.apply(context,args);
            },wait)
        }
    }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享