函数的防抖和节流

柯里化函数:

编程思想,函数执行,产生一个闭包,(不被释放的上下文)把一些信息"预先存储起来",目的是提供下级上下文中调使用,这样的预先存储思想,就叫做柯理化函数编程思想

作用域 vs上下文:

  • 都是函数执行,在栈内存中所分配出来的空间
  • 创建函数的时候,在那个上下文中创建的,那么其作用域就是谁(作用域不是函数自己执行产生的这个空间,而是创建函数所在的这个空间)
  • 而上下文是函数自己执行产生的==>函数执行产生的私有上下文,它的上级上下文是它的作用域

应用场景

    const fn = function fn(...params) {
        // params[1,2]
        return function proxy(...args) {
            // args[3]
            return params.concat(args).reduce((x, item) => x + item)
        }
    }
    let res = fn(1, 2)(3)//fn(1,2)-->proxy(3)
    console.log(res);

 了解:新版浏览器无法实现

add一直持续执行,执行几次是不确定的,我们最后需要把每一次执行的值累加求和 

在浏览器没有生级之前console.log(函数)会把函数转换为字符串输出

函数(Symbol.toPrimitive)-->函数.valueof()--->函数.tostring,但凡其中有一项返回了对象的值则控制台以返回输出为主,但是升级之后,转换为字符串的操作还会触发,但是控制台最后呈现的还是函数

   const curring = function curring() {
        let params = [];
        const add = (...args) => {
            params = params.concat(args);
            return add;
        };
        add[Symbol.toPrimitive] = () => params.reduce((result, item) => result + item);
        return add;
    };
    let add = curring();
    let res = add(1)(2)(3);
    console.log(res); //->6

    add = curring();
    res = add(1, 2, 3)(4);
    console.log(res); //->10

    add = curring();
    res = add(1)(2)(3)(4)(5);
    console.log(res); //->15

函数式编程vs命令式编程

函数式编程:WHAT  把具体执行的步骤封装到一个函数中,后期需要处理的时候,只要把函数执行即可,我们不在关注执行的步骤,只关注后期处理结果

  • 优点:低耦合好内聚,快捷化开发,方便维护
  • 缺点:不能灵活掌握程序处理步骤,无法在某一个步骤做些特殊处理

命令式编程:HOW 更关注处理步骤,需要自己去实现每一步的操作

  • 优点:灵活想怎么处理就怎么处理
  • 缺点:代码冗余度高,开发效率慢

真实项目中推荐使用函数式编程

栗子

    // forEach就是函数式编程,函数内部实现了对数组迭代的封装,每一次迭代都把回调函数执行,并且把当前迭代这一项及索引传递过来
    let arr = [10, 20, 30]
    arr.forEach((item, index) => {
        console.log(item, index);
    });
    // 命令式编程
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i], i);
    }
//封装forEach
    Array.prototype.forEach = function (callback) {
        //this--->要操作的数组
        let self = this;
        for (let i = 0; i < self.length; i++) {
            callback(self[i], i)
        }
    }

面试题:foreach和for循环的区别

forEach是数组内置的方法,内部对数组迭代进行了封装,直接使用就可以 函数式编程,不支持以任何形式中断它的迭代;forEach内部依次迭代每一项,每一次迭代把传递的回调函数执行,把迭代的内容及其索引传递给回调函数..直到这个数字迭代完毕

for循环是命令式编程,需要我们自己手动实现数组迭代,可以中断,可以设置间隔几个迭代

在项目开发的时候一般应用的是forEach,这样可以提高我的开发效率,减少代码冗余,但是遇到一些灵活的代码需求,则自己基于for循环操作

 组合函数

  /* 
        在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
        const add1 = (x) => x + 1;
        const mul3 = (x) => x * 3;
        const div2 = (x) => x / 2;
        div2(mul3(add1(add1(0)))); //=>3
    ​
        而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
        const operate = compose(div2, mul3, add1, add1)
        operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
        operate(2) //=>相当于div2(mul3(add1(add1(2))))
    ​
        简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请你完成 compose函数的编写 
    */
    const add1 = x => x + 1;
    const mul3 = x => x * 3;
    const div2 = x => x / 2;
    const compose = function compose(...funcs) {
        //funcs:数组,一次存储要执行的函数,而且从右到左的顺序执行
        return function operate(x) {
            let len = funcs.length
            // x 函数执行的初始值
            if (len === 0) return x;//如果不传初始值是啥返回啥
            if (len === 1) return funs(0)(x)
            return funcs.reduceRight((x, item) => {
                return item(x)
            }, x)
        }
    };
    const operate = compose(div2, mul3, add1, add1)
    console.log(operate(0));//=>  3  相当于div2(mul3(add1(add1(0)))) 
    console.log(operate(2));//=>6

 惰性函数:懒 执行一次可以搞定,决对不会执行第二次

获取元素样式:

  • getComputedStyle() 方法用于获取指定元素的 CSS 样式。
  • currentStyle ie678
  • 获取的样式是元素在浏览器中最终渲染效果的样式。

封装兼容获取样式方法

瑕疵:浏览器没换,页面没关,第一次处理需要判断兼容性是必须的,但是第二次即以后执行,如果还是要判断兼容性是没必要的

    var getCss = function (elem, attr) {
        if (window.getComputedStyle) {
            getCss = function (elem, attr) {
                return window.getComputedStyle(elem)[attr];
            }
        } else {
            getCss = function (elem, attr) {
                return elem.currentStyle[attr]
            };
        }
        return getCss(elem, attr)

    }

    console.log(getCss(document.body, "margin"));
    console.log(getCss(document.body, "padding"));

,函数的防抖和节流

防抖

防抖:防止"老年帕金森",在用户频繁点击某项操作的时候,我们只识别一次{自定义频繁规则,自定义触发边界}

   const submit = document.getElementById('submit')
    //模拟服务器异步获取数据的操作
    const query = callback => {
        setTimeout(() => {
            callback({
                code: 0,
                msg: "success"
            })
        }, 1000);

    }
    submit.onclick = function () {
        console.log('click,start');
        query(value => {
            console.log(value);
        })
    }

简单版本的防抖处理

点击按钮执行回调函数获取数据,在上一次请求没有完成之前,在次点击按钮不在执行callback获取数据 设置标识

  const submit = document.getElementById('submit')
    let runing = false;//设置标识判断
    //点击按钮执行回调函数获取数据,在上一次请求没有完成之前,在次点击按钮不在执行callback获取数据
    submit.onclick = function () {
        if (runing) return;//如果true 就不在执行
        runing = true;//点击之后把标识改为true
        submit.disabled = true;//禁用按钮
        query(value => {
            console.log(value);
            runing = false;//当获取数据之后,改为false
            submit.disabled = false//恢复按钮禁用
        })
    }

通用防抖方法的封装处理 

思路:

  • 假设:300ms内多次触发事件,算做频繁操作:
  • 第一次点击;等待300ms 设置定时器(执行具体要做的事情)
  • 第二次点击:...
  • 每一次点击都设置一个定时器,等待300ms,300ms之后执行要做的事情
  • 如果定时器还没有到时间,要做的事情还没有干(300m以内)在次触发这个事件,首先把之前设置的定时器清除掉,在重新设置一个新的

 扫盲:如何清除定时器

  • timer是个数字 记录第几个定时器 例如1   clearTimeout(1/2 timer)清除第几个定时器
  • 问题:没有任何的方法直接让我们检测这个定时器是否被清除掉,
  • 所以我们每次设置定时器的时候,一般会把timer设置为null,设置定时器timer是个数字 代表定时器存在,清除定时器我们把timer也设置为null了,那以后我们只要判断timer的值,只要这个值不是null,说明这个定时器没有被清除掉,如果为null是被清除掉的
 timer = setTimeout(() => {
                func();
            }, wait);

代码

  // submit.onclick =operate 疯狂点击,会触发operate函数,我们要做的事情就是保证func(真实要干的事情,只执行一次)
    const clearTimer = function clearTimer(timer) {
        if (timer !== null) clearTimeout(timer)
        return null
    }
    //不传:debounce(函数)-->debounce(函数,300,false) 
    // debounce(函数, 300)-->debounce(函数,300,false)
    // debounce(函数,true)-->debounce(函数,300,false)
    const debounce = function debounce(func, wait, immediate) {
        //func要做的事情  wait时间 事件绑定一定是函数
        if (typeof func !== "function") throw new TypeError('func is not a function!')
        if (typeof wait === "boolean") { immediate = wait; console.log(wait); };
        if (typeof wait !== "number") { wait = 300; console.log(wait); }
        if (typeof immediate !== "boolean") immediate = false;
        let timer = null;
        return function operate(...params) {
            let now = !timer && immediate;
            //清除之前设置的定时器,this-->submit
            timer = clearTimer(timer)
            timer = setTimeout(() => {
                console.log(timer);
                // 清除最后一次设置的定时器
                timer = clearTimer(timer)
                // 设置在结束边界触发
                if (!immediate) func.apply(this, params)
            }, wait);
            // 设置开始边界触发
            if (now) {
                console.log('开始边界', now); return func.apply(this, params)
            }
        }
    }
    //模拟服务器异步获取数据的操作
    const query = callback => {
        setTimeout(() => {
            callback({
                code: 0,
                msg: "success"
            })
        }, 1000);
    }
    const submit = document.getElementById('submit')
    submit.onclick = debounce(function (ev) {
        console.log("click start", ev);
        console.log(this);
        query(value => {
            console.log(value);
        })
    })


节流

  window.onscroll//监听浏览器滚动条事件(默认触发频率 按照浏览器最快反应时间)(谷歌5-7ms触发,这样的触发频率太快了,非常消耗性能)

节流:'降频',在用户频繁点击某项操作的时候,我们降低默认的触发频率

思路window.οnscrοll=throttle 滚动过程中,operate函数会间隔 5ms触发一次,而我们编写throttle方法的目的,是operate执行的过程中,控制要真正执行的func,间隔300ms触发一次

  • 第一次滚动,记录当前触发的时间,@A
  • 间隔5ms第二次触发的时候,拿当前触发时间@A,和300做对比,发现还有295ms才应该去做func这件事,设置一个定时器
  • 第三次及其以后触发,如果发现目前已经有一个等待执行的定时器,就啥也不干即可...,
  • 直到上一次设置的定时器已经执行(两次间隔的时间超过300了),定时器已经被清掉了,此时我们在次设置一个定时器,还是让其等待300ms之后执行
  • 特殊情况 发现两次时间差超过300,此时立即触发

代码


    const clearTimer = function clearTimer(timer) {
        if (timer !== null) clearTimeout(timer)
        return null
    }
    const throttle = function throttle(func, wait) {
        if (typeof func !== "function") throw new TypeError('func is not a function!')
        if (typeof wait === "boolean") { immediate = wait };
        let timer = null,
            previous = 0;//记录上一次触发的时间
        return function operate(...params) {
            let now = +new Date(),//当前时间
                remaining = wait - (now - previous)//两次触发的间隔时间跟300做对比
            if (remaining <= 0) {
                //说明两次操作的间隔时间大于300的,此时立即触发执行
                timer = clearTimer(timer)
                previous = +new Date;//把触发时间记录下来
                func.apply(this, params)
            }
            //间隔时间差不足300,如果还没有设置过定时器,则直接设置定时等着执行,如果已经设置过定时器啥事都不用干
            if (!timer) {
                timer = setTimeout(() => {
                    //清除定时器,保证每次定时器执行完为null
                    timer = clearTimer(timer)
                    previous = +new Date;//把触发时间记录下来
                    func.apply(this, params)
                }, remaining)
            }
        }
    }
    window.onscroll = throttle(function (ev) {
        console.log("scrolling");
    }, 300)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值