自定义工具函数库(二)

132 阅读4分钟

防抖&节流是JS中常见的性能优化手段,当我们在执行一些事件,如浏览器的resize,scroll;鼠标的mousemove,mouseover;input输入框的keypress时,如果我们一直不断的调用绑定在事件上的callback函数,就会极大地浪费性能,严重时会导致页面的卡顿.所以为了优化性能,我们需要对这类事件进行调用次数的限制。

防抖:触发事件后的一段时间后才执行函数,如果在这段时间内被再次触发,则会重新计时

手写防抖第一版

根据定义,我们先使用setTimeout和clearTimeout创建一个基本的防抖函数,根据MDN,调用setTimeout会返回一个timeoutID,我们可以通过clearTimeout清除指定ID的定时器

let timer = null;
function debounce(fn,delay = 1000) {
    clearTimeout(timer);
    timer = setTimeout(function () {
        fn();
    },delay);
}
//test
function testDebounce() {
    console.log('test');
}
document.onmousemove = function () {
    debounce(testDebounce);
}

1.clearTimeout的作用是什么? 清除之前已经存在的debounce,如果不清除则会重复调用之前的debounce,做不到防抖 2.执行多个debounce会有什么问题? 设置多个debounce会导致需要设置多个timer,函数应该实现单一功能原则,保证其自身的高内聚低耦合,所以这个函数设计是不合理的

手写防抖第二版

1.如果要让内部函数可以使用外层函数作用域中的变量,可以使用闭包; 2.arguments指向传入的参数; 3.apply会将调用的函数绑定到apply第一个参数指向的上下文中,并且可以接受一个数组(或类数组对象)作为后续的参数

function debounce(fn,delay = 1000) {
    let timer = null;
    return function() {
        let _this = this;
        args = arguments;
      if(timer) clearTimeout(timer);
      timer = setTimeout(function () {
          fn.apply(_this,args);
      },delay);
    };
}

1.为什么设置_this? 2.为什么要将apply设置到新写的_this上? 在apply中,会将调用的函数绑定到对应的this里,所以如果不使用先前创建的_this,而是直接使用this,(比如调用者此时为input,如果使用this,因为fn是在全局中调用的,则会将调用者指向window,调用apply会将当前的上下文显示的绑定在this上)

function debounce(fn,delay = 1000) {
    let timer = null;
    return function() {
        let _this = this;
        args = arguments;
      if(timer) clearTimeout(timer);
      timer = setTimeout(function () {
          fn(args);//调用fn的是window,而不是我们所预期的对象,如input
          fn.apply(_this,args);//显示地将this指向调用其的函数
      },delay);
    };
}
// test
function testDebounce(e,content) {
    console.log(e,content);
}
var testDebounceFn = debounce(testDebounce);
document.onmousemove = function(e) {
    testDebounceFn(e,'debounce');
}

手写防抖第三版

1.使用箭头函数,将会指向函数定义时的this; 2.箭头函数可以通过扩展运算符避免定义arguments;

function debounce(fn,delay = 1000) {
    let timer = null;
    return(...args) => {
        if(timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn(args);
        },delay);
    };
}

节流:每隔一段时间内,只执行一次函数

手写节流第一版

根据定义,在timer存在的时候直接return,不存在的时候执行setTimeout

function throttle(fn,delay = 1000) {
    let timer = null;
    return function () {
        let _this = this;
        let args = arguments;
        if(timer) return;
        timer = setTimeout(function () {
            fn.apply(_this,args);
            timer = null;
        },delay);
    };
}
// test
function testThrottle(e,content) {
    console.log(e,content);
}
var testThrottleFn = throttle(testThrottle);
document.onmousemove = function(e) {
    testThrottleFn(e,'throttle');
}

手写节流第二版

function throttle(fn,delay = 1000) {
    let timer = null;
    return(...args) => {
        if(timer) return;
        timer = setTimeout(() => {
            fn(args);
            timer = null;
        },delay);
    };
}
// test
function testThrottle(e,content) {
    console.log(e,content);
}
var testThrottleFn = throttle(testThrottle);
document.onmousemove = function(e) {
    testThrottleFn(e,'throttle');
}

同异比较

相同点:1.都使用setTimeout 2.目的都是降低回调的执行频率,节省资源

不同点: 1.防抖关注一定时间内连续触发的事件,只在最后一次执行;节流则侧重于一段时间内只执行一次

使用场景

防抖使用场景:连续的事件,只需触发一次回调的场景;

1.搜索框搜索输入,最后一次输入完成后,再次发送请求;

2.手机号,邮箱的输入验证;

3.窗口大小的调整,在调整完成后计算窗口大小,防止重复渲染;

节流使用场景:间隔一段时间执行一次回调的场景;

1.滚动加载,加载更多的操作;

2.表单的多次点击提交;

复习到的知识点:

1.setTimeout和clearTimeout的使用;

2.使用闭包获取变量,保证函数功能聚合;

3.apply的使用;

4.this指向问题;

5.箭头函数的使用;