前端面试相关算法---防抖节流

311 阅读3分钟

防抖

你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行!

//防抖函数:抖动停止后的时间超过设定的时间时执行一次函数--某固定时间内最多执行一次
function debounce(func, delay) {
    var timeout;
    //返回的函数在一个抖动结束后的delay毫秒内执行func函数
    return function() {
        var context = this, args = arguments; //保存函数调用时的上下文和参数
        clearTimeout(timeout); //触发func前清除定时器
        timeout = setTimeout(function() {
            func.apply(context, args);  //如果不这么做,this指向为window,参数为undefined
        }, delay); //用户停止某个连续动作delay毫秒后执行func
    };
}

防抖优化

我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。因此加个 immediate 参数判断是否是立刻执行。

// 防抖优化
function debounce(func, delay, immediate) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, delay)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, delay);
        }
    }
}

window.addEventListener("resize", debounce(realFunc, 500));
window.addEventListener("resize", debounce(realFunc, 500true));

节流

每隔一段时间执行一次

使用时间戳:

使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

// 使用时间戳
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

使用定时器

// 使用定时器
function throttle(func, wait) {
    var timeout;
    var previous = 0;

    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

比较两个方法:

  1. 使用时间戳会立刻执行,使用定时器会在 n 秒后第一次执行
  2. 使用时间戳停止触发后没有办法再执行事件,使用定时器停止触发后依然会再执行一次事件

双剑合璧:

我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!

// 双剑合璧版
function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
    };

    var throttled = function() {
        var now = +new Date();
        //下次触发 func 剩余的时间
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
         // 如果没有剩余的时间了或者你改了系统时间
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}
//简写版
function throttle(func, delay, mustRun) {
    var timeout;
    var starttime = new Date(); //起始的时间
    return function() {
        var context = this, args = arguments;
        var curtime = new Date(); //当前时间
        clearTimeout(timeout);
        //如果达到规定的触发时间间隔,触发func
        if(curtime - starttime >= mustRun) {
            func.apply(context, args);
            starttime = curtime;
        }
        //没有达到触发时间,重新设定计时器
        else {
            timeout = setTimeout(func, delay);
        }
    }
}

在creat-react-app中封装按钮

源码

import React, { Component } from 'react'

import PropTypes from 'prop-types';

import {Button} from 'antd'

export default class ButtonPage extends Component {

    static defaultProps = {

        children: "Button",

        type: "primary",

        size: "default",

        disabled: false,

        circle: true,

        icon: "",

    };

    static propTypes = {

        children: PropTypes.string,

        type: PropTypes.oneOf(["primary","danger", "normal"]),

        size: PropTypes.oneOf(["default", "small", "large"]),

        disabled: PropTypes.bool,

        circle: PropTypes.bool,

        icon: PropTypes.string,

    };

    // debounce已达到预期效果!

    debounce (f,time) {

        if(f==="") return

        let timer = null

        return () => {

            if(timer) {

                clearTimeout(timer)

            }

            timer = setTimeout(f.bind(this),time)

        }

    }

    // throttle已达到预期效果!

    throttle (f,time ) {

        let timer = null

        return () => {

            if(timer) return

            timer = setTimeout(() => {

                f.apply(this)

                timer = null

            },time)

        }

    }

    render() {

        const { type, size} = this.props;

        return (

            <div>

                <Button

                    type={type}

                    size={size}

                    shape="round"

                    onClick={this.debounce(this.props.onClick?this.props.onClick:"",2000)}

                ></Button>

            </div>

        )

    }

}

使用

import Button from './components/Button/Button'

<Button type="danger" size="small" onClick={() => { console.log("1111") }}/>