כיצד להשתמש ב- Memoize במטמון תוצאות פונקציות JavaScript ולהאיץ את הקוד שלך

פונקציות הן חלק בלתי נפרד מתכנות. הם עוזרים להוסיף מודולריות ושימוש חוזר בקוד שלנו.

זה די נפוץ לחלק את התוכנית שלנו לחתיכות באמצעות פונקציות שאליהן נוכל להתקשר מאוחר יותר כדי לבצע פעולה שימושית כלשהי.

לפעמים, פונקציה יכולה להיות יקרה להתקשרות מספר פעמים (נניח, פונקציה לחישוב הפקטוריון של מספר). אבל יש דרך שנוכל לבצע אופטימיזציה של פונקציות כאלה ולגרום להן לבצע הרבה יותר מהר: מטמון .

לדוגמה, נניח שיש לנו functionלהחזיר את הפקטוריון של מספר:

function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }

נהדר, עכשיו בואו נמצא factorial(50). המחשב יבצע חישובים ויחזיר לנו את התשובה הסופית, מתוק!

כאשר זה נגמר, בואו נמצא factorial(51). המחשב מבצע שוב מספר חישובים ומביא לנו את התוצאה, אך אולי שמתם לב שאנחנו כבר חוזרים על מספר צעדים שאפשר היה למנוע. דרך מותאמת תהיה:

factorial(51) = factorial(50) * 51

אבל שלנו functionמבצע את החישובים מאפס בכל פעם שזה נקרא:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

האם לא יהיה מגניב אם איכשהו factorialהפונקציה שלנו תוכל לזכור את הערכים מחישוביה הקודמים ולהשתמש בהם כדי להאיץ את הביצוע?

נכנס לתזכורת , דרך שלנו functionלזכור (לשמור את המטמון) את התוצאות. עכשיו שיש לך הבנה בסיסית של מה שאנחנו מנסים להשיג, הנה הגדרה רשמית:

שינון היא טכניקת אופטימיזציה המשמשת בעיקר לזרז תוכניות מחשב על ידי אחסון התוצאות של שיחות פונקציה יקרות והחזרת התוצאה במטמון כאשר אותן תשומות מתרחשות שוב.

שינון במילים פשוטות פירושו שינון או אחסון בזיכרון. פונקציה מתוזמנת בדרך כלל מהירה יותר מכיוון שאם הפונקציה נקראת בהמשך עם הערכים הקודמים, במקום לבצע את הפונקציה היינו מביאים את התוצאה מהמטמון.

הנה איך יכולה להיראות פונקציה מתוזמנת פשוטה (והנה CodePen למקרה שתרצו לתקשר איתה) :

// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached

מסירות תזכירים

כמה מההוצאות מהקוד שלעיל הן:

  • memoizedAddמחזיר a functionאשר מופעל מאוחר יותר. זה אפשרי מכיוון שב- JavaScript פונקציות הן אובייקטים ממדרגה ראשונה המאפשרת לנו להשתמש בהן כפונקציות מסדר גבוה יותר ולהחזיר פונקציה אחרת.
  • cacheיכול לזכור את הערכים שלו מכיוון שלפונקציה המוחזרת יש סגירה.
  • זה חיוני שהפונקציה המתוזמנת תהיה טהורה. פונקציה טהורה תחזיר את אותה הפלט עבור קלט מסוים ללא חומר כמה פעמים זה נקרא, מה שהופך את cacheהעבודה כצפוי.

כותב memoizeפונקציה משלך

הקוד הקודם עובד בסדר אבל מה אם היינו רוצים להפוך פונקציה כלשהי לפונקציה מתוזמנת?

כך תכתוב פונקציית תזכורת משלך (codepen):

// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached

עכשיו זה נהדר! memoizeפונקציה פשוטה זו תעטוף כל פשוט functionלשווה ערך. הקוד עובד בסדר עבור פונקציות פשוטות וניתן לשנות אותו בקלות בכדי להתמודד עם כל מספר argumentsלפי הצרכים שלך. אלטרנטיבה נוספת היא להשתמש בכמה ספריות דה-פקטו כגון:

  • של לודאש _.memoize(func, [resolver])
  • @memoizeמעצבי ES7 מבית דקו

שינון פונקציות רקורסיביות

אם תנסה להעביר פונקציה רקורסיבית לפונקציה memoizeשלמעלה או _.memoizeמלודאש, התוצאות לא יהיו כצפויות מכיוון שהפונקציה הרקורסיבית בשיחותיה הבאות תתקשר בסופו של דבר במקום בפונקציה המתוזכנת ובכך לא תשתמש בפונקציה cache.

רק וודא שהפונקציה הרקורסיבית שלך קוראת לפונקציה המתוזכרת. כך תוכל לצבוט דוגמה לפקטוריאל של ספרי הלימוד (codepen):

// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5

כמה נקודות שיש לציין מהקוד הזה:

  • factorialהפונקציה קוראת רקורסיבי גרסת memoized של עוצמה.
  • הפונקציה מתוזמנת מטמנת את ערכי המפעלים הקודמים המשפרת משמעותית את החישובים מכיוון שניתן לעשות בהם שימוש חוזר factorial(6) = 6 * factorial(5)

האם תזכורת זהה למטמון?

כן סוג של. שינון הוא למעשה סוג ספציפי של מטמון. בעוד שמטמון יכול להתייחס באופן כללי לכל טכניקת אחסון (כמו אחסון במטמון HTTP) לשימוש עתידי, שינון בעל ערך כולל כרוך במטמון ערכי החזרה של a function.

מתי תזכיר את הפונקציות שלך

למרות שזה עשוי להיראות כאילו ניתן להשתמש בתזכירים עם כל הפונקציות, למעשה יש לו מקרי שימוש מוגבלים:

  • על מנת לבצע תזכורת לפונקציה, עליה להיות טהורה כך שערכי ההחזרה יהיו זהים לאותן תשומות בכל פעם
  • שינון הוא פשרה בין הוספת שטח למהירות נוספת ולכן משמעותי רק לפונקציות בעלות טווח קלט מוגבל כך שניתן להשתמש בערכים במטמון בתדירות גבוהה יותר.
  • זה עשוי להיראות שכדאי שתזכיר את שיחות ה- API שלך, אולם זה לא הכרחי מכיוון שהדפדפן שומר אותן אוטומטית במטמון עבורך. ראה מטמון HTTP לקבלת פרטים נוספים
  • מקרה השימוש הטוב ביותר שמצאתי עבור פונקציות בעלות תזכורת הוא עבור פונקציות חישוביות כבדות אשר יכולות לשפר משמעותית את הביצועים (פקטוריאליים ו- פיבונאצי הם לא ממש דוגמאות טובות בעולם האמיתי)
  • אם אתה מתייחס ל- React / Redux, תוכל לבדוק את הבחירה מחדש המשתמשת בבורר בעל תזכורת כדי להבטיח שחישובים יתרחשו רק כאשר קורה שינוי בחלק קשור של עץ המדינה.

לקריאה נוספת

הקישורים הבאים יכולים להיות שימושיים אם תרצה לדעת יותר על כמה מהנושאים מתוך מאמר זה בפירוט רב יותר:

  • פונקציות של הזמנה גבוהה יותר ב- JavaScript
  • סגירות ב- JavaScript
  • פונקציות טהורות
  • _.memoizeהמסמכים וקוד המקור של לודאש
  • דוגמאות נוספות לזכירה כאן וכאן
  • reactjs / בחר מחדש

אני מקווה שמאמר זה היה שימושי עבורך, וקבלת הבנה טובה יותר של תזכורת ב- JavaScript :)

אתה יכול לעקוב אחרי בטוויטר לקבלת העדכונים האחרונים. התחלתי גם לפרסם פוסטים אחרונים בבלוג האישי שלי.