日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

函數的防抖和節流&&深淺克隆

作者:前端 賈公子 更新時間: 2022-01-14 編程語言

柯里化函數:

編程思想,函數執行,產生一個閉包,(不被釋放的上下文)把一些信息"預先存儲起來",目的是提供下級上下文中調使用,這樣的預先存儲思想,就叫做柯理化函數編程思想

作用域 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)

原文鏈接:https://blog.csdn.net/qq_63358859/article/details/122261894

欄目分類
最近更新