記憶化是另一個令人生畏的術語,當你理解它時,它就會變得非常直觀。今天我們就來了解什麼是記憶吧!


一些筆記

  • 我製作了本教學的影片版本!在這裡查看一下。

  • 如果您喜歡這篇文章,請考慮訂閱我的免費每週網頁開發電子報


介紹

記憶化是許多程式語言中使用的最佳化技術,用於減少冗餘、昂貴的函數呼叫的數量。這是透過根據函數的輸入快取函數的返回值來完成的。在這篇文章中,我們將建立一個次優但希望具有教育意義的 JavaScript 函數記憶體!

首先,需要記憶的昂貴函數

這是一個供我們記憶的函數。它以非常低效的方式求出數字的平方。

const inefficientSquare = num => {
  let total = 0;
  for (let i = 0; i < num; i++) {
    for (let j = 0; j < num; j++) {
      total++;
    }
  }
  return total;
};

我們可以使用相同的值來執行這個函數,並且每次都需要一段時間來執行。

const start = new Date();
inefficientSquare(40000);
console.log(new Date() - start);
// 1278

const start2 = new Date();
inefficientSquare(40000);
console.log(new Date() - start2);
// 1245

每次都超過一秒,哎呀!

為我們的 Memoizer 編寫偽程式碼

在編寫任何程式碼之前,讓我們先透過記憶體進行推理。

  • 將對函數的引用作為輸入

  • 傳回一個函數(因此可以像平常一樣使用)

  • 建立某種緩存來保存先前函數呼叫的結果

  • 以後任何時候呼叫該函數,都會傳回快取結果(如果存在)

  • 如果快取的值不存在,則呼叫該函數並將結果儲存在快取中

真實程式碼時間

這是上述偽程式碼大綱的實作。正如簡介中提到的,這是次優的,您不應該在生產中使用它。後面我會解釋為什麼!

// Takes a reference to a function
const memoize = func => {
  // Creates a cache of results
  const results = {};
  // Returns a function
  return (...args) => {
    // Create a key for results cache
    const argsKey = JSON.stringify(args);
    // Only execute func if no cached value
    if (!results[argsKey]) {
      // Store function call result in cache
      results[argsKey] = func(...args);
    }
    // Return cached value
    return results[argsKey];
  };
};

此實作中最次優的部分,以及為什麼我不建議在生產程式碼中使用它,是使用JSON.stringifyresults快取中建立鍵。 JSON.stringify的最大問題是它不會序列化某些輸入,例如函數和符號(以及您在 JSON 中找不到的任何內容)。

在昂貴的功能上測試我們的記憶體

讓我們複製inefficientSquare範例,但這次我們將使用 memoizer 來快取結果。

const memoize = func => {
  const results = {};
  return (...args) => {
    const argsKey = JSON.stringify(args);
    if (!results[argsKey]) {
      results[argsKey] = func(...args);
    }
    return results[argsKey];
  };
};

const inefficientSquare = memoize(num => {
  let total = 0;
  for (let i = 0; i < num; i++) {
    for (let j = 0; j < num; j++) {
      total++;
    }
  }
  return total;
});

const start = new Date();
inefficientSquare(40000);
console.log(new Date() - start);
// 1251

const start2 = new Date();
inefficientSquare(40000);
console.log(new Date() - start2);
// 0

成功!第二次我們使用相同的輸入呼叫inefficientSquare時,不需要時間重新計算;我們只是從物件中提取快取的值。

只記住純函數!

記憶化很棒,但只有當你的函數是純函數時才有效。換句話說,如果函數的回傳值不僅僅依賴其輸入,那麼這些輸入的快取值將不會總是正確的。此外,如果您的函數有副作用,記憶體不會複製這些副作用,它只會傳回最終返回的函數值。

結論

現在您應該很清楚我們如何以及為什麼使用記憶化!雖然我們的記憶功能不是最佳的,但您可以使用許多第三方函式庫,它們會做得更好。只要確保您記住的函數是純函數即可!


原文出處:https://dev.to/nas5w/an-introduction-to-memoization-59o


共有 0 則留言