JavaScript寫一個Once函數,讓傳入函數只執行一次
大家好,我是前端西瓜哥,今天我們做一道簡單的前端面試題。
用 JavaScript 實現一個 once 函數,要求傳入函數只能執行一次。且第二次及以后再調用時,仍會返回第一次執行的值。
效果要求如下:
const addOnce = once(function(a, b) {
return a + b;
});
addOnce(1, 2); // 3
addOnce(1, 2999); // 依舊是 3
思路和實現
這里涉及到一個 閉包 的概念。
什么是閉包?閉包是一種技術,它能讓 一個函數訪問另一個函數內的變量(或者叫關聯的環境)。
一種常見的方式就是調用一個函數 a,然后這個函數返回了一個新創建的函數 b。獲得的效果是:新的函數 b 可以訪問到 a 中聲明的變量。
once 函數就要借助閉包的力量,返回 一個綁定了作用域的新函數。
我們先看看實現。
function once(fn) {
let ret; // 緩存結果用
return function(...args) {
if (!fn) return ret;
ret = fn(...args);
fn = undefined; // 表示已經執行過一次
return ret;
}
}
利用閉包,我們返回的新函數有兩個 “私有” 的變量可以訪問:
- 傳入的 fn 函數;
- 額外聲明的用于緩存結果的 ret 變量
當返回的新函數被調用時,我們先將參數傳給 fn,拿到返回值緩存到 ret。然后將 fn 設置為 undefined,用于標識別已經執行了一次,最后返回 ret。
下次再調用時,我們通過判斷 fn 為 falsy,直接返回緩存的 ret。
另外,你貌似可以加多一個對 fn 的類型校驗:typeof fn === 'function',來向面試官表達你的代碼的健壯性。
有一個比較有趣的地方:如果返回的是個對象,多次調用的返回值其實都是指向同一個。如果你希望每次返回的對象都是新的對象,可以考慮返回一個拷貝后的對象(如果可以拷貝的話)。
結尾
once 的實現并不復雜,只要利用閉包,用封閉的環境保存一個緩存的返回值,以及一個是否執行過的狀態,就能控制函數的執行走向。