אופטימיזציה של ביצוע JavaScript

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

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

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

עם זאת, יש כמה דברים שאפשר לעשות כדי לעזור לאפליקציות לבצע את JavaScript בצורה טובה.

סיכום

  • מומלץ להימנע משימוש ב-setTimeout או ב-setInterval לעדכונים חזותיים. במקום זאת, תמיד צריך להשתמש ב-requestAnimationFrame.
  • העברת קוד JavaScript ארוך מהשרשור הראשי ל-Web Workers.
  • שימוש במשימות מיקרו כדי לבצע שינויים ב-DOM במספר פריימים.
  • כדי להעריך את ההשפעה של JavaScript, אפשר להשתמש ב-Timeline וב-JavaScript Profiler בכלי הפיתוח של Chrome.

שימוש ב-requestAnimationFrame לשינויים חזותיים

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

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

מסגרות או דוגמאות עשויות להשתמש ב-setTimeout או ב-setInterval כדי לבצע שינויים חזותיים כמו אנימציות, אבל הבעיה היא שהקריאה החוזרת (callback) תרוץ בשלב כלשהו בפריים, אולי ממש בסוף, ולעיתים קרובות הדבר עלול לגרום לנו להחמיץ פריים, וכתוצאה מכך לתנודות בפריים.

setTimeout גורם לדפדפן לפספס פריים.

למעשה, בעבר jQuery השתמש ב-setTimeout לצורך ההתנהגות של animate. בגרסה 3, הוא השתנה ל-requestAnimationFrame. אם אתם משתמשים בגרסה ישנה יותר של jQuery, תוכלו לתקן אותה כך שתשתמש ב-requestAnimationFrame. מומלץ מאוד לעשות זאת.

הפחתת המורכבות או שימוש ב-Web Workers

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

חשוב להחליט מתי JavaScript יפעל ובמשך כמה זמן. לדוגמה, אם אתם משתמשים באנימציה כמו גלילה, מומלץ לנסות לשמור על זמן JavaScript של 3-4ms. אם תמשיכו יותר מדי זמן, אתם עלולים להקדיש לכך יותר מדי זמן. אם אתם נמצאים בתקופה של חוסר פעילות, תוכלו להמתין זמן רב יותר.

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

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

לא כל העבודה מתאימה למודל הזה: ל-Web Workers אין גישה ל-DOM. במקרים שבהם העבודה חייבת להתבצע בשרשור הראשי, כדאי לשקול גישה של צבירה (batching), שבה מחלקים את המשימה הגדולה למשימות משנה (micro-tasks), שכל אחת מהן נמשכת כמה אלפיות השנייה, ומריצים אותן בתוך מנהלי requestAnimationFrame בכל פריים.

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

מידע על 'מס המסגרות' של JavaScript

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

הדרך הטובה ביותר למדוד את העלות של JavaScript היא באמצעות החלונית 'ביצועים' בכלי הפיתוח ל-Chrome. בדרך כלל מקבלים רשומות ברמה נמוכה כמו זו:

הקלטת ביצועים ב-Chrome DevTools

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

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

במאמר תחילת העבודה עם ניתוח ביצועים בזמן ריצה מוסבר איך להשתמש בחלונית 'ביצועים'.

הימנעות מאופטימיזציה ברמת המיקרו של JavaScript

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

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

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