פונקציות הן חלק בלתי נפרד מתכנות. הם עוזרים להוסיף מודולריות ושימוש חוזר בקוד שלנו.
זה די נפוץ לחלק את התוכנית שלנו לחתיכות באמצעות פונקציות שאליהן נוכל להתקשר מאוחר יותר כדי לבצע פעולה שימושית כלשהי.
לפעמים, פונקציה יכולה להיות יקרה להתקשרות מספר פעמים (נניח, פונקציה לחישוב הפקטוריון של מספר). אבל יש דרך שנוכל לבצע אופטימיזציה של פונקציות כאלה ולגרום להן לבצע הרבה יותר מהר: מטמון .
לדוגמה, נניח שיש לנו 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
מחזיר afunction
אשר מופעל מאוחר יותר. זה אפשרי מכיוון שב- 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 :)
אתה יכול לעקוב אחרי בטוויטר לקבלת העדכונים האחרונים. התחלתי גם לפרסם פוסטים אחרונים בבלוג האישי שלי.