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

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

פול לואיס
פול לואיס

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

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

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

סיכום

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

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

setTimeout וכתוצאה מכך הדפדפן מחמיץ מסגרת.

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

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

JavaScript פועל ב-thread הראשי של הדפדפן, לצד חישובי הסגנון, הפריסה, ובמקרים רבים הצגת צבעים. אם קוד ה-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. כאשר העבודה שלכם צריכה להיות ב-thread הראשי, כדאי לשקול גישת קיבוץ, שבה אתם מפלחים את המשימה הגדולה למיקרו-משימות, שכל אחת מהן נמשכת לא יותר מכמה אלפיות השנייה, ותרוץ בתוך רכיבי ה-handler של requestAnimationFrame בכל פריים.

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

הסבר על "מס מסגרת" של JavaScript

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

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

תיעוד של הביצועים בכלי הפיתוח ל-Chrome

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

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

במאמר תחילת העבודה עם Analyzing Runtime Performance מוסבר איך משתמשים בחלונית הביצועים.

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

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

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

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