国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

探究防抖(debounce)和節(jié)流(throttle)

keke / 2053人閱讀

摘要:如果使用的是防抖,那么得等我們停止?jié)L動(dòng)之后一段時(shí)間才會(huì)加載新的內(nèi)容,沒有那種無(wú)限滾動(dòng)的流暢感。這時(shí)候,我們就可以使用節(jié)流,將事件有效觸發(fā)的頻率降低的同時(shí)給用戶流暢的瀏覽體驗(yàn)。調(diào)用,瀏覽器會(huì)在下次刷新的時(shí)候執(zhí)行指定回調(diào)函數(shù)。

本文來自我的博客,歡迎大家去GitHub上star我的博客

本文從防抖和節(jié)流出發(fā),分析它們的特性,并拓展一種特殊的節(jié)流方式requestAnimationFrame,最后對(duì)lodash中的debounce源碼進(jìn)行分析

防抖和節(jié)流是前端開發(fā)中經(jīng)常使用的一種優(yōu)化手段,它們都被用來控制一段時(shí)間內(nèi)方法執(zhí)行的次數(shù),可以為我們節(jié)省大量不必要的開銷

防抖(debounce)

當(dāng)我們需要及時(shí)獲知窗口大小變化時(shí),我們會(huì)給window綁定一個(gè)resize函數(shù),像下面這樣:

window.addEventListener("resize", () => {
    console.log("resize")
});

我們會(huì)發(fā)現(xiàn),即使是極小的縮放操作,也會(huì)打印數(shù)十次resize,也就是說,如果我們需要在onresize函數(shù)中搞一些小動(dòng)作,也會(huì)重復(fù)執(zhí)行幾十次。但實(shí)際上,我們只關(guān)心鼠標(biāo)松開,窗口停止變化的那一次resize,這時(shí)候,就可以使用debounce優(yōu)化這個(gè)過程:

const handleResize = debounce(() => {
    console.log("resize");
}, 500);
window.addEventListener("resize", handleResize);

運(yùn)行上面的代碼(你得有現(xiàn)成的debounce函數(shù)),在停止縮放操作500ms后,默認(rèn)用戶無(wú)繼續(xù)操作了,才會(huì)打印resize

這就是防抖的功效,它把一組連續(xù)的調(diào)用變?yōu)榱艘粋€(gè),最大程度地優(yōu)化了效率

再舉一個(gè)防抖的常見場(chǎng)景:

搜索欄常常會(huì)根據(jù)我們的輸入,向后端請(qǐng)求,獲取搜索候選項(xiàng),顯示在搜索欄下方。如果我們不使用防抖,在輸入“debounce”時(shí)前端會(huì)依次向后端請(qǐng)求"d"、"de"、"deb"..."debounce"的搜索候選項(xiàng),在用戶輸入很快的情況下,這些請(qǐng)求是無(wú)意義的,可以使用防抖優(yōu)化

觀察上面這兩個(gè)例子,我們發(fā)現(xiàn),防抖非常適于只關(guān)心結(jié)果,不關(guān)心過程如何的情況,它能很好地將大量連續(xù)事件轉(zhuǎn)為單個(gè)我們需要的事件

為了更好理解,下面提供了最簡(jiǎn)單的debounce實(shí)現(xiàn):返回一個(gè)function,第一次執(zhí)行這個(gè)function會(huì)啟動(dòng)一個(gè)定時(shí)器,下一次執(zhí)行會(huì)清除上一次的定時(shí)器并重起一個(gè)定時(shí)器,直到這個(gè)function不再被調(diào)用,定時(shí)器成功跑完,執(zhí)行回調(diào)函數(shù)

const debounce = function(func, wait) {
    let timer;
    return function() {
        !!timer && clearTimeout(timer);
        timer = setTimeout(func, wait);
    };
};

那如果我們不僅關(guān)心結(jié)果,同時(shí)也關(guān)心過程呢?

節(jié)流(throttle)

節(jié)流讓指定函數(shù)在規(guī)定的時(shí)間里執(zhí)行次數(shù)不會(huì)超過一次,也就是說,在連續(xù)高頻執(zhí)行中,動(dòng)作會(huì)被定期執(zhí)行。節(jié)流的主要目的是將原本操作的頻率降低

實(shí)例:

我們模擬一個(gè)可無(wú)限滾動(dòng)的feed流

html:

css:

#wrapper {
    height: 500px;
    overflow: auto;
}
.feed {
    height: 200px;
    background: #ededed;
    margin: 20px;
}

js:

const wrapper = document.getElementById("wrapper");
const loadContent = () => {
    const {
        scrollHeight,
        clientHeight,
        scrollTop
    } = wrapper;
    const heightFromBottom = scrollHeight - scrollTop - clientHeight;
    if (heightFromBottom < 200) {
        const wrapperCopy = wrapper.cloneNode(true);
        const children = [].slice.call(wrapperCopy.children);
        children.forEach(item => {
            wrapper.appendChild(item);
        })
    }
}
const handleScroll = throttle(loadContent, 200);
wrapper.addEventListener("scroll", handleScroll);

可以看到,在這個(gè)例子中,我們需要不停地獲取滾動(dòng)條距離底部的高度,以判斷是否需要增加新的內(nèi)容。我們知道,srcoll同樣也是種會(huì)高頻觸發(fā)的事件,我們需要減少它有效觸發(fā)的次數(shù)。如果使用的是防抖,那么得等我們停止?jié)L動(dòng)之后一段時(shí)間才會(huì)加載新的內(nèi)容,沒有那種無(wú)限滾動(dòng)的流暢感。這時(shí)候,我們就可以使用節(jié)流,將事件有效觸發(fā)的頻率降低的同時(shí)給用戶流暢的瀏覽體驗(yàn)。在這個(gè)例子中,我們指定throttle的wait值為200ms,也就是說,如果你一直在滾動(dòng)頁(yè)面,loadCotent函數(shù)也只會(huì)每200ms執(zhí)行一次

同樣,這里有throttle最簡(jiǎn)單的實(shí)現(xiàn),當(dāng)然,這種實(shí)現(xiàn)很粗糙,有不少缺陷(比如沒有考慮最后一次執(zhí)行),只供初步理解使用:

const throttle = function (func, wait) {
    let lastTime;
    return function () {
        const curTime = Date.now();
        if (!lastTime || curTime - lastTime >= wait) {
            lastTime = curTime;
            return func();
        }
    }
}
requestAnimationFrame(rAF)

rAF在一定程度上和throttle(func,16)的作用相似,但它是瀏覽器自帶的api,所以,它比throttle函數(shù)執(zhí)行得更加平滑。調(diào)用window.requestAnimationFrame(),瀏覽器會(huì)在下次刷新的時(shí)候執(zhí)行指定回調(diào)函數(shù)。通常,屏幕的刷新頻率是60hz,所以,這個(gè)函數(shù)也就是大約16.7ms執(zhí)行一次。如果你想讓你的動(dòng)畫更加平滑,用rAF就再好不過了,因?yàn)樗歉聊坏乃⑿骂l率來的

rAF的寫法與debounce和throttle不同,如果你想用它繪制動(dòng)畫,需要不停地在回調(diào)函數(shù)里調(diào)用自身,具體寫法可以參考mdn

rAF支持ie10及以上瀏覽器,不過因?yàn)槭菫g覽器自帶的api,我們也就無(wú)法在node中使用它了

總結(jié)

debounce將一組事件的執(zhí)行轉(zhuǎn)為最后一個(gè)事件的執(zhí)行,如果你只關(guān)注結(jié)果,debounce再適合不過

如果你同時(shí)關(guān)注過程,可以使用throttle,它可以用來降低高頻事件的執(zhí)行頻率

如果你的代碼是在瀏覽器上運(yùn)行,不考慮兼容ie10,并且要求頁(yè)面上的變化盡可能的平滑,可以使用rAF

參考:https://css-tricks.com/debouncing-throttling-explained-examples/

附:lodash源碼解析

lodash的debounce功能十分強(qiáng)大,集debounce、throttle和rAF于一身,所以我特意研讀一下,下面是我的解析(我刪去了一些不重要的代碼,比如debounced的cancel方法):

function debounce(func, wait, options) {
    /**
     * lastCallTime是上一次執(zhí)行debounced函數(shù)的時(shí)間
     * lastInvokeTime是上一次調(diào)用func的時(shí)間
     */
    let lastArgs, lastThis, maxWait, result, timerId, lastCallTime;

    let lastInvokeTime = 0;
    let leading = false;
    let maxing = false;
    let trailing = true;

    /**
     * 如果沒設(shè)置wait且raf可用 則默認(rèn)使用raf
     */
    const useRAF =
        !wait && wait !== 0 && typeof root.requestAnimationFrame === "function";

    if (typeof func !== "function") {
        throw new TypeError("Expected a function");
    }
    wait = +wait || 0;
    if (isObject(options)) {
        leading = !!options.leading;
        maxing = "maxWait" in options;
        maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;
        trailing = "trailing" in options ? !!options.trailing : trailing;
    }

    /**
     * 執(zhí)行func
     */
    function invokeFunc(time) {
        const args = lastArgs;
        const thisArg = lastThis;

        lastArgs = lastThis = undefined;
        /**
         * 更新lastInvokeTime
         */
        lastInvokeTime = time;
        result = func.apply(thisArg, args);
        return result;
    }

    /**
     * 調(diào)用定時(shí)器
     */
    function startTimer(pendingFunc, wait) {
        if (useRAF) {
            root.cancelAnimationFrame(timerId);
            return root.requestAnimationFrame(pendingFunc);
        }
        return setTimeout(pendingFunc, wait);
    }

    /**
     * 在每輪debounce開始調(diào)用
     */
    function leadingEdge(time) {
        lastInvokeTime = time;
        timerId = startTimer(timerExpired, wait);
        return leading ? invokeFunc(time) : result;
    }

    /**
     * 計(jì)算剩余時(shí)間
     * 1是 wait 減去 距離上次調(diào)用debounced時(shí)間(lastCallTime)
     * 2是 maxWait 減去 距離上次調(diào)用func時(shí)間(lastInvokeTime)
     * 1和2取最小值
     */
    function remainingWait(time) {
        const timeSinceLastCall = time - lastCallTime;
        const timeSinceLastInvoke = time - lastInvokeTime;
        const timeWaiting = wait - timeSinceLastCall;

        return maxing
            ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
            : timeWaiting;
    }

    /**
     * 判斷是否需要執(zhí)行
     */
    function shouldInvoke(time) {
        const timeSinceLastCall = time - lastCallTime;
        const timeSinceLastInvoke = time - lastInvokeTime;
        /**
         * 4種情況返回true,否則返回false
         * 1.第一次調(diào)用
         * 2.距離上次調(diào)用debounced時(shí)間(lastCallTime)>=wait
         * 3.系統(tǒng)時(shí)間倒退
         * 4.設(shè)置了maxWait,距離上次調(diào)用func時(shí)間(lastInvokeTime)>=maxWait
         */
        return (
            lastCallTime === undefined ||
            timeSinceLastCall >= wait ||
            timeSinceLastCall < 0 ||
            (maxing && timeSinceLastInvoke >= maxWait)
        );
    }

    /**
     * 通過shouldInvoke函數(shù)判斷是否執(zhí)行
     * 執(zhí)行:調(diào)用trailingEdge函數(shù)
     * 不執(zhí)行:調(diào)用startTimer函數(shù)重新開始timer,wait值通過remainingWait函數(shù)計(jì)算
     */
    function timerExpired() {
        const time = Date.now();
        if (shouldInvoke(time)) {
            return trailingEdge(time);
        }
        // Restart the timer.
        timerId = startTimer(timerExpired, remainingWait(time));
    }

    /**
     * 在每輪debounce結(jié)束調(diào)用
     */
    function trailingEdge(time) {
        timerId = undefined;

        /**
         * trailing為true且lastArgs不為undefined時(shí)調(diào)用
         */
        if (trailing && lastArgs) {
            return invokeFunc(time);
        }
        lastArgs = lastThis = undefined;
        return result;
    }

    function debounced(...args) {
        const time = Date.now();
        const isInvoking = shouldInvoke(time);

        lastArgs = args;
        lastThis = this;
        /**
         * 更新lastCallTime
         */
        lastCallTime = time;

        if (isInvoking) {
            /**
             * 第一次調(diào)用
             */
            if (timerId === undefined) {
                return leadingEdge(lastCallTime);
            }
            /**
             * 【注1】
             */
            if (maxing) {
                timerId = startTimer(timerExpired, wait);
                return invokeFunc(lastCallTime);
            }
        }
        /**
         * 【注2】
         */
        if (timerId === undefined) {
            timerId = startTimer(timerExpired, wait);
        }
        return result;
    }
    return debounced;
}

推薦是從返回的方法debounced開始,順著執(zhí)行順序閱讀,理解起來更輕松

【注1】一開始我沒看明白if(maxing)里面這段代碼的作用,按理說,是不會(huì)執(zhí)行這段代碼的,后來我去lodash的倉(cāng)庫(kù)里看了test文件,發(fā)現(xiàn)對(duì)這段代碼,專門有一個(gè)case對(duì)其測(cè)試。我剝除了一些代碼,并修改了測(cè)試用例以便展示,如下:

var limit = 320,
    withCount = 0

var withMaxWait = debounce(function () {
    console.log("invoke");
    withCount++;
}, 64, {
    "maxWait": 128
});

var start = +new Date;
while ((new Date - start) < limit) {
    withMaxWait();
}

執(zhí)行代碼,打印了3次invoke;我又將if(maxing){}這段代碼注釋,再執(zhí)行代碼,結(jié)果只打印了1次。結(jié)合源碼的英文注釋Handle invocations in a tight loop,我們不難理解,原本理想的執(zhí)行順序是withMaxWait->timer->withMaxWait->timer這種交替進(jìn)行,但由于setTimeout需等待主線程的代碼執(zhí)行完畢,所以這種短時(shí)間快速調(diào)用就會(huì)導(dǎo)致withMaxWait->withMaxWait->timer->timer,從第二個(gè)timer開始,由于lastArgs被置為undefined,也就不會(huì)再調(diào)用invokeFunc函數(shù),所以只會(huì)打印一次invoke。

同時(shí),由于每次執(zhí)行invokeFunc時(shí)都會(huì)將lastArgs置為undefined,在執(zhí)行trailingEdge時(shí)會(huì)對(duì)lastArgs進(jìn)行判斷,確保不會(huì)出現(xiàn)執(zhí)行了if(maxing){}中的invokeFunc函數(shù)又執(zhí)行了timer的invokeFunc函數(shù)

這段代碼保證了設(shè)置maxWait參數(shù)后的正確性和時(shí)效性

【注2】執(zhí)行過一次trailingEdge后,再執(zhí)行debounced函數(shù),可能會(huì)遇到shouldInvoke返回false的情況,需多帶帶處理

【注3】對(duì)于lodash的debounce來說,throttle是一種leading為true且maxWait等于wait的特殊debounce

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/101866.html

相關(guān)文章

  • 函數(shù)防抖Debounce函數(shù)節(jié)流Throttle

    摘要:函數(shù)節(jié)流函數(shù)防抖函數(shù)節(jié)流和函數(shù)防抖函數(shù)節(jié)流和函數(shù)防抖二者很容易被混淆起來。函數(shù)防抖函數(shù)在特定的時(shí)間內(nèi)不被再調(diào)用后執(zhí)行。一句話概括函數(shù)節(jié)流是從用戶開始輸入就開始計(jì)時(shí),而函數(shù)節(jié)流是從用戶停止輸入開始計(jì)時(shí)。 函數(shù)節(jié)流 & 函數(shù)防抖 函數(shù)節(jié)流和函數(shù)防抖 函數(shù)節(jié)流和函數(shù)防抖二者很容易被混淆起來。下面貼英文原文,建議認(rèn)真閱讀:Debouncing enforces that a function ...

    NicolasHe 評(píng)論0 收藏0
  • 從lodash源碼學(xué)習(xí)節(jié)流防抖

    摘要:首先重置防抖函數(shù)最后調(diào)用時(shí)間,然后去觸發(fā)一個(gè)定時(shí)器,保證后接下來的執(zhí)行。這就避免了手動(dòng)管理定時(shí)器。 ??之前遇到過一個(gè)場(chǎng)景,頁(yè)面上有幾個(gè)d3.js繪制的圖形。如果調(diào)整瀏覽器可視區(qū)大小,會(huì)引發(fā)圖形重繪。當(dāng)圖中的節(jié)點(diǎn)比較多的時(shí)候,頁(yè)面會(huì)顯得異常卡頓。為了限制類似于這種短時(shí)間內(nèi)高頻率觸發(fā)的情況,我們可以使用防抖函數(shù)。 ??實(shí)際開發(fā)過程中,這樣的情況其實(shí)很多,比如: 頁(yè)面的scroll事件 ...

    CloudDeveloper 評(píng)論0 收藏0
  • debounce(防抖)throttle(節(jié)流)

    摘要:防抖防抖,簡(jiǎn)單來說就是防止抖動(dòng)。兩者間的核心區(qū)別就在于持續(xù)觸發(fā)事件時(shí),前者合并事件并在最后時(shí)間去觸發(fā)事件,而后者則是隔間時(shí)間觸發(fā)一次關(guān)鍵知識(shí)點(diǎn)定時(shí)器閉包資源在線測(cè)試源代碼 防抖和節(jié)流 窗口的resize、scroll,輸入框內(nèi)容校驗(yàn)等操作時(shí),如果這些操作處理函數(shù)較為復(fù)雜或頁(yè)面頻繁重渲染等操作時(shí),如果事件觸發(fā)的頻率無(wú)限制,會(huì)加重瀏覽器的負(fù)擔(dān),導(dǎo)致用戶體驗(yàn)非常糟糕。此時(shí)我們可以采用debo...

    2bdenny 評(píng)論0 收藏0
  • debounce(防抖)throttle(節(jié)流)

    摘要:防抖防抖,簡(jiǎn)單來說就是防止抖動(dòng)。兩者間的核心區(qū)別就在于持續(xù)觸發(fā)事件時(shí),前者合并事件并在最后時(shí)間去觸發(fā)事件,而后者則是隔間時(shí)間觸發(fā)一次關(guān)鍵知識(shí)點(diǎn)定時(shí)器閉包資源在線測(cè)試源代碼 防抖和節(jié)流 窗口的resize、scroll,輸入框內(nèi)容校驗(yàn)等操作時(shí),如果這些操作處理函數(shù)較為復(fù)雜或頁(yè)面頻繁重渲染等操作時(shí),如果事件觸發(fā)的頻率無(wú)限制,會(huì)加重瀏覽器的負(fù)擔(dān),導(dǎo)致用戶體驗(yàn)非常糟糕。此時(shí)我們可以采用debo...

    ssshooter 評(píng)論0 收藏0
  • 說說JavaScript中函數(shù)的防抖 (Debounce) 與節(jié)流 (Throttle)

    摘要:基礎(chǔ)防抖我們現(xiàn)在寫一個(gè)最基礎(chǔ)的防抖處理標(biāo)記事件也做如下改寫現(xiàn)在試一下,我們會(huì)發(fā)現(xiàn)只有我們停止?jié)L動(dòng)秒鐘的時(shí)候,控制臺(tái)才會(huì)打印出一行隨機(jī)數(shù)。 為何要防抖和節(jié)流 有時(shí)候會(huì)在項(xiàng)目開發(fā)中頻繁地觸發(fā)一些事件,如 resize、 scroll、 keyup、 keydown等,或者諸如輸入框的實(shí)時(shí)搜索功能,我們知道如果事件處理函數(shù)無(wú)限制調(diào)用,會(huì)大大加重瀏覽器的工作量,有可能導(dǎo)致頁(yè)面卡頓影響體驗(yàn);后臺(tái)...

    yanwei 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<