最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript防抖與節(jié)流超詳細(xì)全面講解

 更新時(shí)間:2022年10月20日 10:09:08   作者:橘貓吃不胖~  
在開發(fā)中我們經(jīng)常會遇到一些高頻操作,比如:鼠標(biāo)移動,滑動窗口,鍵盤輸入等等,節(jié)流和防抖就是對此類事件進(jìn)行優(yōu)化,降低觸發(fā)的頻率,以達(dá)到提高性能的目的。本文就教大家如何實(shí)現(xiàn)一個(gè)讓面試官拍大腿的防抖節(jié)流函數(shù),需要的可以參考一下

1 為什么需要防抖和節(jié)流

在前端開發(fā)當(dāng)中,有些交互事件,會被頻繁觸發(fā),這樣會導(dǎo)致我們的頁面渲染性能下降,如果頻繁觸發(fā)接口調(diào)用的話,會直接導(dǎo)致服務(wù)器性能的浪費(fèi)。

舉個(gè)例子,在下面的代碼中,我們定義了一個(gè)輸入框,輸入一段文字,測試鍵盤的keyup(鍵盤彈起)事件觸發(fā)了多少次,通過該實(shí)例來演示事件是如何被頻繁觸發(fā)的。

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <script>
        // 獲取input輸入框與span標(biāo)簽
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        demo.onkeyup = function () {
            // 將span標(biāo)簽中的文本修改為事件被觸發(fā)的次數(shù)
            count.innerHTML = ++init;
        }
    </script>

從上面的演示可以看到,我在輸入框中輸入了5個(gè)字,但是keyup事件會被觸發(fā)30次。如果我們使用這樣的方式去檢測用戶輸入的用戶名是否可用,這樣高頻率的觸發(fā)不僅是對性能極大的浪費(fèi),而且用戶還沒有輸入完就開始檢測,對用戶來說提示并不友好。在這樣的情況下,我們就可以等用戶輸入完成之后,再去觸發(fā)函數(shù),這樣的優(yōu)化就使用到了防抖與節(jié)流。

2 防抖與節(jié)流原理

函數(shù)防抖:在事件觸發(fā)后的 n 秒之后,再去執(zhí)行真正需要執(zhí)行的函數(shù),如果在這 n 秒之內(nèi)事件又被觸發(fā),則重新開始計(jì)時(shí)。 也就是說,如果用戶在間隔時(shí)間內(nèi)一直觸發(fā)函數(shù),那么這個(gè)防抖函數(shù)內(nèi)部的真正需要執(zhí)行的函數(shù)將永遠(yuǎn)無法執(zhí)行。

那么根據(jù)防抖的原理,我們可以嘗試想象一下上面的例子的改進(jìn)措施,如果為keyup事件添加防抖函數(shù),那么只有當(dāng)keyup在一段時(shí)間內(nèi)不再被觸發(fā),函數(shù)才會執(zhí)行,也就說才開始計(jì)數(shù)。

函數(shù)節(jié)流:規(guī)定好一個(gè)單位時(shí)間,觸發(fā)函數(shù)一次。如果在這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù)的話,只有一次是可被執(zhí)行的。想執(zhí)行多次的話,只能等到下一個(gè)周期里。

如果為keyup事件添加節(jié)流函數(shù),那么效果就是,在一段時(shí)間內(nèi),會計(jì)數(shù)一次,然后在下一段時(shí)間內(nèi),再計(jì)數(shù)一次。

在了解防抖函數(shù)和節(jié)流函數(shù)的原理之后,接下來我們可以嘗試自己寫一個(gè)防抖與節(jié)流的函數(shù),看看是否能達(dá)到我們預(yù)想的效果。

3 實(shí)現(xiàn)一個(gè)防抖函數(shù)

3.1 初步實(shí)現(xiàn)

根據(jù)之前的描述,在事件被觸發(fā)一段時(shí)間之后,函數(shù)才會執(zhí)行一次,那么防抖函數(shù)中我們應(yīng)該為其傳入兩個(gè)參數(shù):被執(zhí)行的函數(shù)fun和這段時(shí)間time。

// fun:被執(zhí)行的函數(shù)
// time:間隔的時(shí)間
function debounce(fun, time) { }

對于防抖函數(shù)來說,它的返回值應(yīng)該是一個(gè)函數(shù),因?yàn)槭录|發(fā)時(shí)接收一個(gè)函數(shù)。在該函數(shù)內(nèi)部,要設(shè)計(jì)一個(gè)定時(shí)器,讓在time時(shí)間后觸發(fā)函數(shù)fun。

function debounce(fun, time) {
    return function () {
        // time時(shí)間后觸發(fā)函數(shù)fun
        setTimeout(fun, time);
    }
}

但是上面的函數(shù)有一個(gè)問題,就是事件再次被觸發(fā)時(shí),會出現(xiàn)time時(shí)間后再執(zhí)行一次函數(shù)fun,不能達(dá)到事件觸發(fā)完成time時(shí)間后再執(zhí)行函數(shù)的效果,也就是說,事件會被延時(shí)觸發(fā),并不能減少觸發(fā),這是因?yàn)槎〞r(shí)器效果進(jìn)行了累加,因此我們需要取消之前的定時(shí)器,以新的定時(shí)器為準(zhǔn)。

function debounce(fun, time) {
    let timer;
    return function () {
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        // time時(shí)間后觸發(fā)函數(shù)fun
        timer = setTimeout(fun, time);
    }
}

到這里一個(gè)初步的防抖函數(shù)就完成了,接下來使用該函數(shù)改進(jìn)之前的例子,具體代碼如下:

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <script>
        // 獲取input輸入框與span標(biāo)簽
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        // 防抖函數(shù)
        function debounce(fun, time) {
            let timer;
            return function () {
                // 取消當(dāng)前的定時(shí)器效果
                clearTimeout(timer);
                // time時(shí)間后觸發(fā)函數(shù)fun
                timer = setTimeout(fun, time);
            }
        }
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        demo.onkeyup = debounce(function () {
            // 將span標(biāo)簽中的文本修改為事件被觸發(fā)的次數(shù)
            count.innerHTML = ++init;
        }, 1000);
    </script>

3.2 this問題

從上面的效果圖來看,我輸入5個(gè)字后,1秒后keyup事件就觸發(fā)了1次,對比之前的30次,大大減少了事件的觸發(fā)頻率。但是添加防抖之后,原本函數(shù)的this指向發(fā)生了改變。原本函數(shù)的this指向了觸發(fā)事件的那個(gè)對象,但是添加防抖后this指向了window。

// 添加防抖之前打印 this
demo.onkeyup = function () {
    console.log(this); // <input type="text" id="demo"></input>
}

// 添加防抖之后打印 this
demo.onkeyup = debounce(function () {
    console.log(this);
}, 1000);

因此在防抖函數(shù)中,我們需要重新把this指回觸發(fā)事件的對象上。那防抖函數(shù)中返回的函數(shù)this指向了誰呢,我們可以打印一下:

        function debounce(fun, time) {
            return function () {
                console.log(this);
            }
        }

我們發(fā)現(xiàn)它的this也指向了觸發(fā)事件的對象,那么接下來我們只需要讓定時(shí)器的回調(diào)函數(shù)的this指向觸發(fā)事件的對象就可以,這個(gè)過程主要使用call函數(shù)來修改this的指向。

function debounce(fun, time) {
    let timer;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        // time時(shí)間后觸發(fā)函數(shù)fun
        timer = setTimeout(function () {
            fun.call(that); // 使用call改變函數(shù)內(nèi)部的this指向
        }, time);
    }
}

3.3 event問題

解決了this指向的問題,接下來觀察事件對象event的內(nèi)容,添加防抖之前,事件對象event是鍵盤事件KeyboardEvent,但是添加防抖之后,event為undefined。

// 添加防抖之前
demo.onkeyup = function (e) {
    console.log(e);
}

// 添加防抖后
demo.onkeyup = debounce(function (e) {
    console.log(e);
}, 1000);

同樣的操作,我們可以打印一下防抖函數(shù)返回的函數(shù)的arguments參數(shù),發(fā)現(xiàn)參數(shù)中就包含了事件對象。

function debounce(fun, time) {
    console.log(arguments);
}

那么接下來我們將這個(gè)參數(shù)傳給函數(shù)fun就可以了,具體傳給call函數(shù)。call函數(shù)第二個(gè)參數(shù)開始接受其他的參數(shù),因此需要使用spread運(yùn)算符(…)傳遞參數(shù)。

function debounce(fun, time) {
    let timer;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        // time時(shí)間后觸發(fā)函數(shù)fun
        timer = setTimeout(function () {
            // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
            fun.call(that, ...args);
        }, time);
    }
}

3.4 立即執(zhí)行

到這一步防抖函數(shù)基本可以完成了,但是我們可以再為其添加一些功能,比如說立即執(zhí)行。當(dāng)設(shè)置了立即執(zhí)行之后,第一次事件觸發(fā)后,函數(shù)fun會立即執(zhí)行,但是第一次事件觸發(fā)后的time時(shí)間后,函數(shù)才可以重新觸發(fā)。

我們可以傳遞第三個(gè)參數(shù),第三個(gè)參數(shù)immediate決定了是否立即執(zhí)行,true為是,false為否。那么代碼邏輯就可以使用if…else…語句來進(jìn)行判斷。我們原本的防抖函數(shù)肯定不是立即執(zhí)行的,因此放在else語句中。

function debounce(fun, time, immediate) {
    let timer;
    return function () {
        let that = this; // 將當(dāng)前的this賦值給that
        let args = arguments; // 獲取函數(shù)的參數(shù)
        clearTimeout(timer); // 取消當(dāng)前的定時(shí)器效果
        if (immediate) {
            // 立即執(zhí)行代碼
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
    }
}

if語句中的代碼不是簡單的fun.call(that, ...args);就可以,因?yàn)楫?dāng)immediate為true時(shí),就會一直調(diào)用,與不加防抖沒什么區(qū)別。因此我們可以引入新的變量callNow,來記錄是否要立即執(zhí)行。

function debounce(fun, time, immediate) {
    let timer;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        if (immediate) { // 立即執(zhí)行
            let callNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            }, time);
            if (callNow) fun.call(that, ...args);
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
    }
}

if語句中的具體邏輯為:當(dāng)immediate為true時(shí),如果之前計(jì)時(shí)器不存在,也就是說第一次觸發(fā),那么callNow的值為true,那么代碼就會立即執(zhí)行;計(jì)時(shí)器存在,callNow就是false,不會立即執(zhí)行代碼。接下來可以在keyup事件中試驗(yàn)一下:

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <script>
        // 獲取input輸入框與span標(biāo)簽
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        // 防抖函數(shù)
        function debounce(fun, time, immediate) {
            let timer;
            return function () {
                // 將當(dāng)前的this賦值給that
                let that = this;
                // 獲取函數(shù)的參數(shù)
                let args = arguments;
                // 取消當(dāng)前的定時(shí)器效果
                clearTimeout(timer);
                if (immediate) { // 立即執(zhí)行
                    let callNow = !timer;
                    timer = setTimeout(function () {
                        timer = null;
                    }, time);
                    if (callNow) fun.call(that, ...args);
                } else { // 不立即執(zhí)行
                    // time時(shí)間后觸發(fā)函數(shù)fun
                    timer = setTimeout(function () {
                        // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                        fun.call(that, ...args);
                    }, time);
                }
            }
        }
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        demo.onkeyup = debounce(function () {
            // 將span標(biāo)簽中的文本修改為事件被觸發(fā)的次數(shù)
            count.innerHTML = ++init;
        }, 1000, true);
    </script>

從上面效果可以看出,在輸入第一個(gè)1時(shí),事件就立即觸發(fā)了,在接下來的1秒內(nèi)事件不再被觸發(fā),而是在事件被觸發(fā)的1秒之后才可以繼續(xù)觸發(fā)。

3.5 返回值問題

如果被執(zhí)行的函數(shù)有返回值,使用上面的防抖函數(shù)就沒辦法獲取到返回值了,因此可以繼續(xù)改進(jìn):

function debounce(fun, time, immediate) {
    // result用來獲取返回值
    let timer, result;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        if (immediate) { // 立即執(zhí)行
            let callNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            }, time);
            if (callNow) result = fun.call(that, ...args);
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
        return result;
    }
}

3.6 取消防抖

如果一個(gè)防抖函數(shù)等待的時(shí)間過長,immediate為true,那么我們可以取消防抖,然后再去觸發(fā),這樣就可以減少等待時(shí)間。

在代碼中我們將防抖返回的函數(shù)保存在變量debounced中,并且為它增加一個(gè)cancel方法,通過該方法可以取消當(dāng)前的定時(shí)器,從而實(shí)現(xiàn)取消的效果。

function debounce(fun, time, immediate) {
    // result用來獲取返回值
    let timer, result;
    let debounced = function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        if (immediate) { // 立即執(zhí)行
            let callNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            }, time);
            if (callNow) result = fun.call(that, ...args);
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
        return result;
    }
    debounced.cancel = function () {
        clearTimeout(timer); // 清除定時(shí)器
        timer = null; // 閉包會導(dǎo)致內(nèi)存泄漏,因此需要將定時(shí)器制空
    }
    return debounced; // 返回防抖函數(shù)
}

使用keyup事件試驗(yàn)一下,當(dāng)沒有取消防抖時(shí),一段時(shí)間后才可以再次觸發(fā)事件:

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <button id="btn">取消防抖</button>
    <script>
        // 獲取input輸入框、span標(biāo)簽、按鈕
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        let btn = document.getElementById("btn");
        // 防抖代碼函數(shù)省略
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        function fun() { // 觸發(fā)keyup后要執(zhí)行的函數(shù)
            count.innerHTML = ++init;
        }
        let fd = debounce(fun, 3000, true);
        demo.onkeyup = fd; // 為輸入框注冊keyup事件
        btn.onclick = function () { // 取消防抖的效果
            fd.cancel();
        }
    </script>

當(dāng)取消防抖函數(shù)之后,就可以立即觸發(fā)事件了:

3.7 總結(jié)

初步防抖函數(shù),解決了this指向以及event參數(shù)的問題:

function debounce(fun, time) {
    let timer;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        // time時(shí)間后觸發(fā)函數(shù)fun
        timer = setTimeout(function () {
            // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
            fun.call(that, ...args);
        }, time);
    }
}

增加了立即執(zhí)行效果的防抖函數(shù):

function debounce(fun, time, immediate) {
    let timer;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        if (immediate) { // 立即執(zhí)行
            let callNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            }, time);
            if (callNow) fun.call(that, ...args);
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
    }
}

解決了返回值問題的防抖函數(shù):

function debounce(fun, time, immediate) {
    // result用來獲取返回值
    let timer, result;
    return function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        if (immediate) { // 立即執(zhí)行
            let callNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            }, time);
            if (callNow) result = fun.call(that, ...args);
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
        return result;
    }
}

增加了取消功能的防抖函數(shù):

function debounce(fun, time, immediate) {
    // result用來獲取返回值
    let timer, result;
    let debounced = function () {
        // 將當(dāng)前的this賦值給that
        let that = this;
        // 獲取函數(shù)的參數(shù)
        let args = arguments;
        // 取消當(dāng)前的定時(shí)器效果
        clearTimeout(timer);
        if (immediate) { // 立即執(zhí)行
            let callNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            }, time);
            if (callNow) result = fun.call(that, ...args);
        } else { // 不立即執(zhí)行
            // time時(shí)間后觸發(fā)函數(shù)fun
            timer = setTimeout(function () {
                // 使用call改變函數(shù)內(nèi)部的this指向,并傳遞參數(shù)
                fun.call(that, ...args);
            }, time);
        }
        return result;
    }
    debounced.cancel = function () {
        clearTimeout(timer); // 清除定時(shí)器
        timer = null; // 閉包會導(dǎo)致內(nèi)存泄漏,因此需要將定時(shí)器制空
    }
    return debounced; // 返回防抖函數(shù)
}

4 實(shí)現(xiàn)節(jié)流函數(shù)

4.1 通過時(shí)間戳實(shí)現(xiàn)節(jié)流

當(dāng)觸發(fā)事件的時(shí)候,我們?nèi)〕霎?dāng)前的時(shí)間戳,然后減去之前的時(shí)間戳(時(shí)間戳初始值為0),如果大于設(shè)置的時(shí)間time,就執(zhí)行函數(shù)fun,然后更新時(shí)間戳為當(dāng)前的時(shí)間戳,如果小于time,就不執(zhí)行函數(shù)。

根據(jù)上面的表述,節(jié)流函數(shù)有兩個(gè)參數(shù),一個(gè)是要執(zhí)行的函數(shù)fun,一個(gè)是等待的時(shí)間time,那么就可以寫出初始的代碼:

function throttle(fun, time) {
    // 節(jié)流代碼
}

節(jié)流函數(shù)的返回值也是一個(gè)函數(shù),首先要設(shè)置初始時(shí)間戳為0,然后獲取當(dāng)前的時(shí)間戳,如果間隔的時(shí)間大于time,那么就執(zhí)行函數(shù),否則不執(zhí)行。

function throttle(fun, time) {
    let old = 0;
    return function () {
        let now = new Date().valueOf(); // 獲取當(dāng)前的時(shí)間戳
        if (now - old > time) {
            fun(); // 執(zhí)行函數(shù)
            old = now; // 更新舊時(shí)間戳
        }
    }
}

與防抖函數(shù)相同,要考慮到this指向和event改變的情況,因此引入兩個(gè)變量,使用call函數(shù)更改this指向并且重新傳入?yún)?shù)。

function throttle(fun, time) {
    let that, args;
    let old = 0;
    return function () {
        let now = new Date().valueOf(); // 獲取當(dāng)前的時(shí)間戳
        that = this; // 獲取this
        args = arguments; // 獲取參數(shù)
        if (now - old > time) {
            fun.apply(that, args); // 更改this指向并傳入?yún)?shù)
            old = now; // 更新舊時(shí)間戳
        }
    }
}

示例代碼:節(jié)流函數(shù)效果

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <script>
        // 獲取input輸入框、span標(biāo)簽
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        // 節(jié)流函數(shù)
        function throttle(fun, time) {
            let that, args;
            let old = 0;
            return function () {
                let now = new Date().valueOf(); // 獲取當(dāng)前的時(shí)間戳
                that = this; // 獲取this
                args = arguments; // 獲取參數(shù)
                if (now - old > time) {
                    fun.apply(that, args); // 更改this指向并傳入?yún)?shù)
                    old = now; // 更新舊時(shí)間戳
                }
            }
        }
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        demo.onkeyup = throttle(function () {
            count.innerHTML = ++init;
        }, 1000);
    </script>

4.2 使用定時(shí)器實(shí)現(xiàn)節(jié)流

節(jié)流函數(shù)有兩個(gè)參數(shù),并且返回值是一個(gè)函數(shù),會修改this指向和event事件對象,那么它的框架就可以理出來了:

function throttle(fun, time) {
    let that, args;
    return function () {
        that = this;
        args = arguments;
        fun.apply(that, args);
    }
}

函數(shù)應(yīng)該在定時(shí)器的回調(diào)函數(shù)中調(diào)用,因此還需要聲明一個(gè)定時(shí)器變量timer,當(dāng)定時(shí)器不存在時(shí),觸發(fā)定時(shí)器,調(diào)用函數(shù)。

function throttle(fun, time) {
    // timer是定時(shí)器對象
    let that, args, timer;
    return function () {
        that = this;
        args = arguments;
        if (!timer) {
            timer = setTimeout(function () {
                fun.apply(that, args);
            }, time);
        }
    }
}

但是當(dāng)定時(shí)器timer一旦觸發(fā),就會永遠(yuǎn)有值,不可能再觸發(fā)定時(shí)器了,因此需要在定時(shí)器回調(diào)函數(shù)中將time置為空。

function throttle(fun, time) {
    // timer是定時(shí)器對象
    let that, args, timer;
    return function () {
        that = this;
        args = arguments;
        if (!timer) {
            timer = setTimeout(function () {
                timer = null;
                fun.apply(that, args);
            }, time);
        }
    }
}

示例代碼:查看函數(shù)效果

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <script>
        // 獲取input輸入框、span標(biāo)簽、按鈕
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        // 節(jié)流函數(shù)
        function throttle(fun, time) {
            // timer是定時(shí)器對象
            let that, args, timer;
            return function () {
                that = this;
                args = arguments;
                if (!timer) {
                    timer = setTimeout(function () {
                        timer = null;
                        fun.apply(that, args);
                    }, time);
                }
            }
        }
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        demo.onkeyup = throttle(function () {
            count.innerHTML = ++init;
        }, 1000);
    </script>

4.3 時(shí)間戳和定時(shí)器組合實(shí)現(xiàn)

從上面的效果可以看出,時(shí)間戳?xí)r間的節(jié)流函數(shù),第一次輸入文本會立即觸發(fā),但是當(dāng)輸入結(jié)束后就不再觸發(fā)了,定時(shí)器實(shí)現(xiàn)的節(jié)流函數(shù),第一次輸入文本要等待一段時(shí)間后再觸發(fā),但是當(dāng)輸入結(jié)束之后還會再觸發(fā)一遍。那么接下來實(shí)現(xiàn)一個(gè)第一次輸入文本會立即觸發(fā),但是輸入結(jié)束之后還會再次觸發(fā)的節(jié)流函數(shù)。

首先將兩個(gè)防抖函數(shù)合并一下:

function throttle(fun, time) {
    let that, args, timer;
    let old = 0; // 設(shè)置初始時(shí)間戳
    return function () {
        that = this;
        args = arguments;
        let now = new Date().valueOf(); // 獲取初始的時(shí)間戳
        if (now - old > time) {
            fun.apply(that, args);
            old = now;
        }
        if (!timer) {
            timer = setTimeout(function () {
                timer = null;
                fun.apply(that, args);
            }, time);
        }
    }
}

這個(gè)防抖函數(shù)的時(shí)間戳和定時(shí)器同時(shí)在運(yùn)行,那我們可以在定時(shí)器時(shí)間戳內(nèi)部,定時(shí)器內(nèi)回調(diào)函數(shù)執(zhí)行一次,就將old的值設(shè)置為最新的時(shí)間戳。這樣就可以讓時(shí)間戳和定時(shí)器節(jié)流函數(shù)時(shí)間同步。

function throttle(fun, time) {
    let that, args, timer;
    let old = 0; // 設(shè)置初始時(shí)間戳
    return function () {
        that = this;
        args = arguments;
        let now = new Date().valueOf(); // 獲取初始的時(shí)間戳
        if (now - old > time) {
            fun.apply(that, args);
            old = now;
        }
        if (!timer) {
            timer = setTimeout(function () {
                old = new Date().valueOf(); // 將old的值設(shè)置為最新的時(shí)間戳
                timer = null;
                fun.apply(that, args);
            }, time);
        }
    }
}

但是不應(yīng)該讓兩個(gè)同步,接下來就執(zhí)行定時(shí)器的防抖函數(shù),在時(shí)間戳防抖函數(shù)中把定時(shí)器取消掉置為空。

function throttle(fun, time) {
    let that, args, timer;
    let old = 0; // 設(shè)置初始時(shí)間戳
    return function () {
        that = this;
        args = arguments;
        let now = new Date().valueOf(); // 獲取初始的時(shí)間戳
        if (now - old > time) {
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            fun.apply(that, args);
            old = now;
        }
        if (!timer) {
            timer = setTimeout(function () {
                old = new Date().valueOf(); // 將old的值設(shè)置為最新的時(shí)間戳
                timer = null;
                fun.apply(that, args);
            }, time);
        }
    }
}

示例代碼:查看節(jié)流函數(shù)的效果

    <input type="text" id="demo">
    <div>觸發(fā)了:<span id="count">0</span>次</div>
    <script>
        // 獲取input輸入框、span標(biāo)簽
        let demo = document.getElementById("demo");
        let count = document.getElementById("count");
        // 節(jié)流函數(shù)省略
        // 為demo輸入框注冊keyup事件
        let init = 0; // 記錄keyup事件被觸發(fā)的次數(shù)
        demo.onkeyup = throttle(function () {
            count.innerHTML = ++init;
        }, 1000);
    </script>

4.4 節(jié)流優(yōu)化

如果我們希望設(shè)計(jì)一個(gè)防抖函數(shù),可以根據(jù)不同的情況來選擇不同的防抖函數(shù),也就是說,對上面三種情況再進(jìn)行一個(gè)結(jié)合。那么我們可以設(shè)置options為第三個(gè)參數(shù),根據(jù)傳的值判斷使用哪種防抖函數(shù)。options可以有兩個(gè)參數(shù):leading,表示是否打開第一次執(zhí)行;trailing:表示是否打開最后一次執(zhí)行。

function throttle(fun, time, options) { // options決定使用哪種節(jié)流效果
    let that, args, timer;
    let old = 0; // 設(shè)置初始時(shí)間戳
    if (!options) options = {}; // 如果沒有該參數(shù),置為空對象
    return function () {
        that = this;
        args = arguments;
        let now = new Date().valueOf(); // 獲取初始的時(shí)間戳
        // leading為false,表示不打開第一次執(zhí)行
        if (options.leading === false && !old) {
            old = now; // 這樣會將下面的時(shí)間戳節(jié)流代碼跳過
        }
        if (now - old > time) { // 第一次回直接執(zhí)行
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            fun.apply(that, args);
            old = now;
        }
        // trailing為false,表示不打開最后一次執(zhí)行
        if (!timer && options.trailing !== false) { // 最后一次會被執(zhí)行
            timer = setTimeout(function () {
                old = new Date().valueOf(); // 將old的值設(shè)置為最新的時(shí)間戳
                timer = null;
                fun.apply(that, args);
            }, time);
        }
    }
}

示例代碼:節(jié)流函數(shù)的使用效果,打開第一次執(zhí)行和最后一次執(zhí)行

        demo.onkeyup = throttle(function () {
            count.innerHTML = ++init;
        }, 1000, { leading: true, trailing: true }); // 表示打開第一次執(zhí)行和最后一次執(zhí)行

打開第一次執(zhí)行,關(guān)閉最后一次執(zhí)行:

        demo.onkeyup = throttle(function () {
            count.innerHTML = ++init;
        }, 1000, { leading: true, trailing: false }); // 表示打開第一次執(zhí)行,關(guān)閉最后一次執(zhí)行

關(guān)閉第一次執(zhí)行,打開最后一次執(zhí)行:

        demo.onkeyup = throttle(function () {
            count.innerHTML = ++init;
        }, 1000, { leading: false, trailing: true }); // 表示打開第一次執(zhí)行,關(guān)閉最后一次執(zhí)行

如果兩個(gè)都關(guān)閉,會出現(xiàn)bug,因此使用時(shí)不會將兩個(gè)都關(guān)閉。

5 應(yīng)用場景

防抖應(yīng)用場景:

  • scroll事件滾動觸發(fā)
  • 搜索框輸入查詢
  • 表單驗(yàn)證
  • 按鈕提交事件
  • 瀏覽器窗口縮放,resize事件

節(jié)流應(yīng)用場景:

  • DOM元素的拖拽功能實(shí)現(xiàn)
  • 射擊游戲
  • 計(jì)算鼠標(biāo)的移動距離
  • 監(jiān)聽scroll滾動事件

本文學(xué)習(xí)于視頻:手寫函數(shù)防抖和節(jié)流

到此這篇關(guān)于JavaScript防抖與節(jié)流超詳細(xì)全面講解的文章就介紹到這了,更多相關(guān)JavaScript防抖與節(jié)流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JavaScript實(shí)現(xiàn)獲取img的原始尺寸的方法詳解

    JavaScript實(shí)現(xiàn)獲取img的原始尺寸的方法詳解

    在微信小程序開發(fā)時(shí),它的image標(biāo)簽有一個(gè)默認(rèn)高度,這樣你的圖片很可能出現(xiàn)被壓縮變形的情況,所以就需要獲取到圖片的原始尺寸對image的寬高設(shè)置,本文就來分享一下JavaScript實(shí)現(xiàn)獲取img的原始尺寸的方法吧
    2023-03-03
  • axios對外出口API的設(shè)計(jì)方法

    axios對外出口API的設(shè)計(jì)方法

    本文我們講解了 axios 對外出口 API 是如何設(shè)計(jì)的,我們首先介紹了內(nèi)部 Axios 類的實(shí)現(xiàn),這是 axios 核心邏輯所在,其次為了讓出口 API 更好使用,最后又在增加了 create() 方法,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2024-05-05
  • JS常見構(gòu)造模式實(shí)例對比分析

    JS常見構(gòu)造模式實(shí)例對比分析

    這篇文章主要介紹了JS常見構(gòu)造模式,結(jié)合實(shí)例形式對比分析了工廠模式、構(gòu)造函數(shù)模式、原型模式、寄生構(gòu)造函數(shù)模式、穩(wěn)妥構(gòu)造函數(shù)模式等相關(guān)概念、原理、實(shí)現(xiàn)方法與相關(guān)操作技巧,需要的朋友可以參考下
    2018-08-08
  • uniapp小程序使用RSA加密解密的操作代碼

    uniapp小程序使用RSA加密解密的操作代碼

    這篇文章主要介紹了uniapp小程序使用RSA加密解密,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • js單線程的本質(zhì) Event Loop解析

    js單線程的本質(zhì) Event Loop解析

    這篇文章主要介紹了js單線程的本質(zhì) Event Loop解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • Javascript 訪問樣式表實(shí)現(xiàn)代碼

    Javascript 訪問樣式表實(shí)現(xiàn)代碼

    Javascript訪問樣式表--又一篇好文章
    2009-08-08
  • JavaScript實(shí)現(xiàn)九九乘法表的簡單實(shí)例

    JavaScript實(shí)現(xiàn)九九乘法表的簡單實(shí)例

    下面小編就為大家?guī)硪黄狫avaScript實(shí)現(xiàn)九九乘法表的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • 淺談DOM的操作以及性能優(yōu)化問題-重繪重排

    淺談DOM的操作以及性能優(yōu)化問題-重繪重排

    下面小編就為大家?guī)硪黄獪\談DOM的操作以及性能優(yōu)化問題-重繪重排。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • JavaScript 異步調(diào)用框架 (Part 5 - 鏈?zhǔn)綄?shí)現(xiàn))

    JavaScript 異步調(diào)用框架 (Part 5 - 鏈?zhǔn)綄?shí)現(xiàn))

    在上一篇文章里面,我們?yōu)楫惒秸{(diào)用框架設(shè)計(jì)了一種鏈?zhǔn)秸{(diào)用方式,來增強(qiáng)異步調(diào)用隊(duì)列的代碼可讀性,現(xiàn)在我們就來編寫實(shí)現(xiàn)這部分功能的代碼。
    2009-08-08
  • 真見識了-全代碼編寫的圖片

    真見識了-全代碼編寫的圖片

    真見識了-全代碼編寫的圖片...
    2007-08-08

最新評論

霸州市| 吴旗县| 合川市| 周口市| SHOW| 安远县| 龙游县| 迁西县| 彰化县| 土默特右旗| 宁安市| 邵阳县| 桐庐县| 赤壁市| 鹿邑县| 苏州市| 晋城| 尉氏县| 宜宾县| 遵化市| 榆树市| 古蔺县| 固镇县| 化州市| 井陉县| 天门市| 吉木萨尔县| 佛坪县| 嵊州市| 岑溪市| 吴堡县| 清徐县| 秦安县| 道真| 宜丰县| 开平市| 会昌县| 寻甸| 康定县| 上虞市| 泰顺县|