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

學(xué)無先后,達者為師

網(wǎng)站首頁 編程語言 正文

函數(shù)的防抖和節(jié)流&&深淺克隆

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

柯里化函數(shù):

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

作用域 vs上下文:

  • 都是函數(shù)執(zhí)行,在棧內(nèi)存中所分配出來的空間
  • 創(chuàng)建函數(shù)的時候,在那個上下文中創(chuàng)建的,那么其作用域就是誰(作用域不是函數(shù)自己執(zhí)行產(chǎn)生的這個空間,而是創(chuàng)建函數(shù)所在的這個空間)
  • 而上下文是函數(shù)自己執(zhí)行產(chǎn)生的==>函數(shù)執(zhí)行產(chǎn)生的私有上下文,它的上級上下文是它的作用域

應(yīng)用場景

    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);

?了解:新版瀏覽器無法實現(xiàn)

add一直持續(xù)執(zhí)行,執(zhí)行幾次是不確定的,我們最后需要把每一次執(zhí)行的值累加求和?

在瀏覽器沒有生級之前console.log(函數(shù))會把函數(shù)轉(zhuǎn)換為字符串輸出

函數(shù)(Symbol.toPrimitive)-->函數(shù).valueof()--->函數(shù).tostring,但凡其中有一項返回了對象的值則控制臺以返回輸出為主,但是升級之后,轉(zhuǎn)換為字符串的操作還會觸發(fā),但是控制臺最后呈現(xiàn)的還是函數(shù)

   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

函數(shù)式編程vs命令式編程

函數(shù)式編程:WHAT? 把具體執(zhí)行的步驟封裝到一個函數(shù)中,后期需要處理的時候,只要把函數(shù)執(zhí)行即可,我們不在關(guān)注執(zhí)行的步驟,只關(guān)注后期處理結(jié)果

  • 優(yōu)點:低耦合好內(nèi)聚,快捷化開發(fā),方便維護
  • 缺點:不能靈活掌握程序處理步驟,無法在某一個步驟做些特殊處理

命令式編程:HOW 更關(guān)注處理步驟,需要自己去實現(xiàn)每一步的操作

  • 優(yōu)點:靈活想怎么處理就怎么處理
  • 缺點:代碼冗余度高,開發(fā)效率慢

真實項目中推薦使用函數(shù)式編程

栗子

    // forEach就是函數(shù)式編程,函數(shù)內(nèi)部實現(xiàn)了對數(shù)組迭代的封裝,每一次迭代都把回調(diào)函數(shù)執(zhí)行,并且把當前迭代這一項及索引傳遞過來
    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--->要操作的數(shù)組
        let self = this;
        for (let i = 0; i < self.length; i++) {
            callback(self[i], i)
        }
    }

面試題:foreach和for循環(huán)的區(qū)別

forEach是數(shù)組內(nèi)置的方法,內(nèi)部對數(shù)組迭代進行了封裝,直接使用就可以 函數(shù)式編程,不支持以任何形式中斷它的迭代;forEach內(nèi)部依次迭代每一項,每一次迭代把傳遞的回調(diào)函數(shù)執(zhí)行,把迭代的內(nèi)容及其索引傳遞給回調(diào)函數(shù)..直到這個數(shù)字迭代完畢

for循環(huán)是命令式編程,需要我們自己手動實現(xiàn)數(shù)組迭代,可以中斷,可以設(shè)置間隔幾個迭代

在項目開發(fā)的時候一般應(yīng)用的是forEach,這樣可以提高我的開發(fā)效率,減少代碼冗余,但是遇到一些靈活的代碼需求,則自己基于for循環(huán)操作

?組合函數(shù)

  /* 
        在函數(shù)式編程當中有一個很重要的概念就是函數(shù)組合, 實際上就是把處理數(shù)據(jù)的函數(shù)像管道一樣連接起來, 然后讓數(shù)據(jù)穿過管道得到最終的結(jié)果。 例如:
        const add1 = (x) => x + 1;
        const mul3 = (x) => x * 3;
        const div2 = (x) => x / 2;
        div2(mul3(add1(add1(0)))); //=>3
    ?
        而這樣的寫法可讀性明顯太差了,我們可以構(gòu)建一個compose函數(shù),它接受任意多個函數(shù)作為參數(shù)(這些函數(shù)都只接受一個參數(shù)),然后compose返回的也是一個函數(shù),達到以下的效果:
        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函數(shù)的編寫 
    */
    const add1 = x => x + 1;
    const mul3 = x => x * 3;
    const div2 = x => x / 2;
    const compose = function compose(...funcs) {
        //funcs:數(shù)組,一次存儲要執(zhí)行的函數(shù),而且從右到左的順序執(zhí)行
        return function operate(x) {
            let len = funcs.length
            // x 函數(shù)執(zhí)行的初始值
            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

?惰性函數(shù):懶 執(zhí)行一次可以搞定,決對不會執(zhí)行第二次

獲取元素樣式:

  • getComputedStyle() 方法用于獲取指定元素的 CSS 樣式。
  • currentStyle ie678
  • 獲取的樣式是元素在瀏覽器中最終渲染效果的樣式。

封裝兼容獲取樣式方法

瑕疵:瀏覽器沒換,頁面沒關(guān),第一次處理需要判斷兼容性是必須的,但是第二次即以后執(zhí)行,如果還是要判斷兼容性是沒必要的

    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"));

,函數(shù)的防抖和節(jié)流

防抖

防抖:防止"老年帕金森",在用戶頻繁點擊某項操作的時候,我們只識別一次{自定義頻繁規(guī)則,自定義觸發(fā)邊界}

   const submit = document.getElementById('submit')
    //模擬服務(wù)器異步獲取數(shù)據(jù)的操作
    const query = callback => {
        setTimeout(() => {
            callback({
                code: 0,
                msg: "success"
            })
        }, 1000);

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

簡單版本的防抖處理

點擊按鈕執(zhí)行回調(diào)函數(shù)獲取數(shù)據(jù),在上一次請求沒有完成之前,在次點擊按鈕不在執(zhí)行callback獲取數(shù)據(jù) 設(shè)置標識

  const submit = document.getElementById('submit')
    let runing = false;//設(shè)置標識判斷
    //點擊按鈕執(zhí)行回調(diào)函數(shù)獲取數(shù)據(jù),在上一次請求沒有完成之前,在次點擊按鈕不在執(zhí)行callback獲取數(shù)據(jù)
    submit.onclick = function () {
        if (runing) return;//如果true 就不在執(zhí)行
        runing = true;//點擊之后把標識改為true
        submit.disabled = true;//禁用按鈕
        query(value => {
            console.log(value);
            runing = false;//當獲取數(shù)據(jù)之后,改為false
            submit.disabled = false//恢復(fù)按鈕禁用
        })
    }

通用防抖方法的封裝處理?

思路:

  • 假設(shè):300ms內(nèi)多次觸發(fā)事件,算做頻繁操作:
  • 第一次點擊;等待300ms 設(shè)置定時器(執(zhí)行具體要做的事情)
  • 第二次點擊:...
  • 每一次點擊都設(shè)置一個定時器,等待300ms,300ms之后執(zhí)行要做的事情
  • 如果定時器還沒有到時間,要做的事情還沒有干(300m以內(nèi))在次觸發(fā)這個事件,首先把之前設(shè)置的定時器清除掉,在重新設(shè)置一個新的

?掃盲:如何清除定時器

  • timer是個數(shù)字 記錄第幾個定時器 例如1???clearTimeout(1/2 timer)清除第幾個定時器
  • 問題:沒有任何的方法直接讓我們檢測這個定時器是否被清除掉,
  • 所以我們每次設(shè)置定時器的時候,一般會把timer設(shè)置為null,設(shè)置定時器timer是個數(shù)字 代表定時器存在,清除定時器我們把timer也設(shè)置為null了,那以后我們只要判斷timer的值,只要這個值不是null,說明這個定時器沒有被清除掉,如果為null是被清除掉的
 timer = setTimeout(() => {
                func();
            }, wait);

代碼

  // submit.onclick =operate 瘋狂點擊,會觸發(fā)operate函數(shù),我們要做的事情就是保證func(真實要干的事情,只執(zhí)行一次)
    const clearTimer = function clearTimer(timer) {
        if (timer !== null) clearTimeout(timer)
        return null
    }
    //不傳:debounce(函數(shù))-->debounce(函數(shù),300,false) 
    // debounce(函數(shù), 300)-->debounce(函數(shù),300,false)
    // debounce(函數(shù),true)-->debounce(函數(shù),300,false)
    const debounce = function debounce(func, wait, immediate) {
        //func要做的事情  wait時間 事件綁定一定是函數(shù)
        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;
            //清除之前設(shè)置的定時器,this-->submit
            timer = clearTimer(timer)
            timer = setTimeout(() => {
                console.log(timer);
                // 清除最后一次設(shè)置的定時器
                timer = clearTimer(timer)
                // 設(shè)置在結(jié)束邊界觸發(fā)
                if (!immediate) func.apply(this, params)
            }, wait);
            // 設(shè)置開始邊界觸發(fā)
            if (now) {
                console.log('開始邊界', now); return func.apply(this, params)
            }
        }
    }
    //模擬服務(wù)器異步獲取數(shù)據(jù)的操作
    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);
        })
    })


節(jié)流

??window.onscroll//監(jiān)聽瀏覽器滾動條事件(默認觸發(fā)頻率 按照瀏覽器最快反應(yīng)時間)(谷歌5-7ms觸發(fā),這樣的觸發(fā)頻率太快了,非常消耗性能)

節(jié)流:'降頻',在用戶頻繁點擊某項操作的時候,我們降低默認的觸發(fā)頻率

思路window.οnscrοll=throttle 滾動過程中,operate函數(shù)會間隔 5ms觸發(fā)一次,而我們編寫throttle方法的目的,是operate執(zhí)行的過程中,控制要真正執(zhí)行的func,間隔300ms觸發(fā)一次

  • 第一次滾動,記錄當前觸發(fā)的時間,@A
  • 間隔5ms第二次觸發(fā)的時候,拿當前觸發(fā)時間@A,和300做對比,發(fā)現(xiàn)還有295ms才應(yīng)該去做func這件事,設(shè)置一個定時器
  • 第三次及其以后觸發(fā),如果發(fā)現(xiàn)目前已經(jīng)有一個等待執(zhí)行的定時器,就啥也不干即可...,
  • 直到上一次設(shè)置的定時器已經(jīng)執(zhí)行(兩次間隔的時間超過300了),定時器已經(jīng)被清掉了,此時我們在次設(shè)置一個定時器,還是讓其等待300ms之后執(zhí)行
  • 特殊情況 發(fā)現(xiàn)兩次時間差超過300,此時立即觸發(fā)

代碼


    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;//記錄上一次觸發(fā)的時間
        return function operate(...params) {
            let now = +new Date(),//當前時間
                remaining = wait - (now - previous)//兩次觸發(fā)的間隔時間跟300做對比
            if (remaining <= 0) {
                //說明兩次操作的間隔時間大于300的,此時立即觸發(fā)執(zhí)行
                timer = clearTimer(timer)
                previous = +new Date;//把觸發(fā)時間記錄下來
                func.apply(this, params)
            }
            //間隔時間差不足300,如果還沒有設(shè)置過定時器,則直接設(shè)置定時等著執(zhí)行,如果已經(jīng)設(shè)置過定時器啥事都不用干
            if (!timer) {
                timer = setTimeout(() => {
                    //清除定時器,保證每次定時器執(zhí)行完為null
                    timer = clearTimer(timer)
                    previous = +new Date;//把觸發(fā)時間記錄下來
                    func.apply(this, params)
                }, remaining)
            }
        }
    }
    window.onscroll = throttle(function (ev) {
        console.log("scrolling");
    }, 300)

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

欄目分類
最近更新