Javascript閉包的4種高級用法

函數(shù)修飾器是一個高階函數(shù),它將一個函數(shù)作為參數(shù)并返回另一個函數(shù),并且返回的函數(shù)是參數(shù)函數(shù)的變體。

提高編程能力最好的方式就是去閱讀并學(xué)習(xí)開源框架或者腳本庫,今天我們就來學(xué)習(xí)underscore.jslodash.jsramda.js之類的庫中利用閉包原理實現(xiàn)函數(shù)修飾器,從中受益匪淺。

一個函數(shù)和對其周圍狀態(tài)(lexical environment,詞法環(huán)境)的引用捆綁在一起(或者說函數(shù)被引用包圍),這樣的組合就是閉包(closure)。也就是說,閉包讓你可以在一個內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域。在 JavaScript 中,每當(dāng)創(chuàng)建一個函數(shù),閉包就會在函數(shù)創(chuàng)建的同時被創(chuàng)建出來。

once()

在之前的文章中曾經(jīng)寫過一個類似的函數(shù)once()。但是這里實現(xiàn)的功能有點不一樣,這里執(zhí)行一次并返回函數(shù)的執(zhí)行結(jié)果。

function once(fn) {
    let returnValue;
    let canRun = true;
    return function runOnce() {
        if (canRun) {
            returnValue = fn.apply(this, arguments);
            canRun = false;
        }
        return returnValue;
    };
}
function process(title) {
    console.log({
        title,
    });
}
const processonce = once(process);
const title = "DevPoint";
processonce(title);
processonce(title);
processonce(title);

上面代碼執(zhí)行結(jié)果只會輸出一次:{ title: 'DevPoint' }

once()是一個返回另一個函數(shù)的函數(shù)。返回的函數(shù)runOnce()是一個閉包。同樣重要的是要注意原始函數(shù)是如何被調(diào)用的——通過傳入this的當(dāng)前值和所有參數(shù)argumentsfn.apply(this, arguments)。可以應(yīng)用的場景可以是搶購,如現(xiàn)在比較多的活動搶茅臺,可以減少瘋狂點擊發(fā)送請求。

after()

after(count, fn):創(chuàng)建僅在多次調(diào)用后才執(zhí)行的函數(shù)方法。例如,當(dāng)想要確保函數(shù)只在完成count次異步操作完成后才運行時,這個函數(shù)就非常實用。

function after(count, fn) {
    let runCount = 0;
    return function runAfter() {
        runCount = runCount + 1;
        if (runCount >= count) {
            return fn.apply(this, arguments);
        }
    };
}
function end() {
    console.log("異步操作結(jié)束了!");
}
const endAfter3Calls = after(3, end); // 定義在執(zhí)行3次異步操作后執(zhí)行函數(shù)logResult

setTimeout(() => {
    console.log("=>完成第一次異步操作");
    endAfter3Calls();
}, 3000);
setTimeout(() => {
    console.log("=>完成第二次異步操作");
    endAfter3Calls();
}, 2000);
setTimeout(() => {
    console.log("=>完成第三次異步操作");
    endAfter3Calls();
}, 6000);

上面代碼執(zhí)行輸出結(jié)果如下:

=>完成第二次異步操作
=>完成第一次異步操作
=>完成第三次異步操作
異步操作結(jié)束了!

這個方法在前端需要做一系列動畫效果的時候很實用。

節(jié)流:throttle()

throttle(fn, wait):用于限制函數(shù)觸發(fā)的頻率,每個delay時間間隔,最多只能執(zhí)行函數(shù)一次。一個最常見的例子是在監(jiān)聽resize/scroll事件時,為了性能考慮,需要限制回調(diào)執(zhí)行的頻率,此時便會使用throttle函數(shù)進(jìn)行限制,也是我們常說的節(jié)流。

function throttle(fn, interval) {
    let lastTime;
    return function throttled() {
        const timeSinceLastExecution = Date.now() - lastTime;
        if (!lastTime || timeSinceLastExecution >= interval) {
            fn.apply(this, arguments);
            lastTime = Date.now();
        }
    };
}
function process() {
    console.log("DevPoint");
}
const throttledProcess = throttle(process, 1000);

for (let i = 0, len = 100; i < len; i++) {
    throttledProcess();
}

防抖:debounce()

debounce(fn, wait):可以減少函數(shù)觸發(fā)的頻率,但限制的方式有點不同。當(dāng)函數(shù)觸發(fā)時,使用一個定時器延遲執(zhí)行操作。當(dāng)函數(shù)被再次觸發(fā)時,清除已設(shè)置的定時器,重新設(shè)置定時器。如果上一次的延遲操作還未執(zhí)行,則會被清除。

function debounce(fn, interval) {
    let timer;
    const debounced = () => {
        clearTimeout(timer);
        const args = arguments;
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, interval);
    };
    return debounced;
}
function process() {
    console.log("DevPoint");
}
const delayProcess = debounce(process, 400);

for (let i = 0, len = 100; i < len; i++) {
    delayProcess();
}

throttle函數(shù)與debounce函數(shù)的區(qū)別就是throttle函數(shù)在觸發(fā)后會馬上執(zhí)行,而debounce函數(shù)會在一定延遲后才執(zhí)行。從觸發(fā)開始到延遲結(jié)束,只執(zhí)行函數(shù)一次。

partial()

現(xiàn)在創(chuàng)建對所有函數(shù)都可用的partial()方法。這里使用了ECMAScript 6 rest參數(shù)語法,而不是參數(shù) arguments 對象,下面實現(xiàn)連接數(shù)組和參數(shù)不是數(shù)組對象。

Function.prototype.partial = function (...leftArguments) {
    let fn = this;
    return function partialFn(...rightArguments) {
        let args = leftArguments.concat(rightArguments);
        return fn.apply(this, args);
    };
};
function log(level, message) {
    console.log(level + " :" + message);
}
const logInfo = log.partial("描述");
logInfo("DevPoint開發(fā)技術(shù)要點");

實現(xiàn)這些常見的函數(shù)可以幫助我們更好地理解修飾器的工作方式,并讓我們了解它可以封裝的邏輯類型。

函數(shù)修飾器是一種強(qiáng)大的工具,可以在不修改原始函數(shù)的情況下創(chuàng)建現(xiàn)有函數(shù)的變體。它們可以作為函數(shù)編程工具箱的一部分,用于重用公共邏輯。