הסבר על אפליקציית האינטרנט
אפליקציות אינטרנט עם ביצועים גבוהים הן חיוניות לחוויית משתמש מעולה. ככל שאפליקציות אינטרנט הופכות למורכבות יותר, חשוב להבין את ההשפעה על הביצועים כדי ליצור חוויה משכנעת. בשנים האחרונות, הופיעו בדפדפן כמה ממשקי API שונים שעוזרים לנתח את ביצועי הרשת, זמני הטעינה וכו', אבל הם לא בהכרח מספקים פרטים מפורטים עם גמישות מספקת כדי למצוא את מה שמאט את האפליקציה. מזינים את User Timing API, שמספק מנגנון שמאפשר להוסיף לאפליקציית האינטרנט כלים לזיהוי המקומות שבהם האפליקציה מבלה את זמנה. במאמר הזה נסביר על ה-API ונציג דוגמאות לשימוש בו.
אי אפשר לבצע אופטימיזציה בלי למדוד
השלב הראשון בהאצת אפליקציית אינטרנט איטית הוא להבין איפה הזמן מתבזבז. מדידת ההשפעה של אזורים בקוד JavaScript על הזמן היא הדרך האידיאלית לזהות נקודות חמות, וזה השלב הראשון בחיפוש דרכים לשיפור הביצועים. למרבה המזל, User Timing API מאפשר להוסיף קריאות ל-API בחלקים שונים של קוד ה-JavaScript, ואז לחלץ נתוני תזמון מפורטים שיכולים לעזור לכם לבצע אופטימיזציה.
זמן ברזולוציה גבוהה ו-now()
חלק בסיסי במדידת זמן מדויקת הוא דיוק. בעבר, התזמון התבסס על מדידה של אלפיות השנייה, וזה בסדר, אבל כדי לבנות אתר ללא בעיות בממשק (jank) בקצב של 60 FPS, צריך לצייר כל פריים ב-16 אלפיות השנייה. לכן, כשמסתמכים רק על דיוק של אלפיות השנייה, חסר הדיוק שנדרש לניתוח טוב. High Resolution Time (זמן ברזולוציה גבוהה) הוא סוג חדש של תזמון שמובנה בדפדפנים מודרניים. זמן ברזולוציה גבוהה נותן לנו חותמות זמן של נקודה צפה (floating point) שיכולות להיות מדויקות עד לרזולוציה של מיקרו-שנייה – פי אלף יותר טוב מבעבר.
כדי לקבל את השעה הנוכחית באפליקציית האינטרנט, צריך להפעיל את ה-method now() שיוצרת תוסף של הממשק Performance. הקוד הבא מראה איך לעשות את זה:
var myTime = window.performance.now();
יש ממשק נוסף שנקרא PerformanceTiming, שמספק כמה זמנים שונים שקשורים לטעינה של אפליקציית האינטרנט. ה-method now() מחזירה את הזמן שחלף מאז שהתרחש navigationStarttime ב-PerformanceTiming.
הסוג DOMHighResTimeStamp
בעבר, כדי למדוד את הזמן של אפליקציות אינטרנט, הייתם משתמשים במשהו כמו Date.now() שמחזיר DOMTimeStamp. הפונקציה DOMTimeStamp מחזירה מספר שלם של אלפיות השנייה כערך שלה. כדי לספק את הדיוק הגבוה יותר שנדרש לזמן ברזולוציה גבוהה, הוכנס סוג חדש שנקרא DOMHighResTimeStamp. הסוג הזה הוא ערך של נקודה צפה (float) שגם מחזיר את הזמן באלפיות השנייה. אבל מכיוון שמדובר בנקודה צפה, הערך יכול לייצג שברים של אלפיות שנייה, וכך להניב דיוק של אלפית השנייה.
ממשק תזמוני המשתמש
עכשיו, כשיש לנו חותמות זמן ברזולוציה גבוהה, נשתמש בממשק תזמוני משתמש כדי לשלוף מידע על תזמון.
ממשק תזמוני המשתמש מספק פונקציות שמאפשרות לנו להפעיל שיטות במקומות שונים באפליקציה, שיכולות לספק נתיב של פירורי לחם בסגנון עמי ותמי כדי לאפשר לנו לעקוב אחרי המקומות שבהם המשתמשים מבלים את הזמן שלהם.
שימוש ב-mark()
השיטה mark() היא הכלי העיקרי בערכת הכלים שלנו לניתוח תזמון. mark() שומר חותמת זמן. היתרון הגדול של mark() הוא שאנחנו יכולים לתת שם לחותמת הזמן, וה-API יזכור את השם ואת חותמת הזמן כיחידה אחת.
קריאה ל-mark() במקומות שונים באפליקציה מאפשרת לכם לחשב כמה זמן לקח לכם להגיע ל"סימון" הזה באפליקציית האינטרנט.
במפרט מוצעים כמה שמות לסימונים שיכולים להיות מעניינים ודי מובנים מאליהם, כמו mark_fully_loaded, mark_fully_visible, mark_above_the_fold וכו'.
לדוגמה, אפשר להגדיר סימון למועד שבו האפליקציה נטענת במלואה באמצעות הקוד הבא:
window.performance.mark('mark_fully_loaded');
הגדרת סימנים עם שמות באפליקציית האינטרנט שלנו מאפשרת לנו לאסוף הרבה נתוני תזמון ולנתח אותם בזמננו הפנוי כדי להבין מה האפליקציה עושה ומתי.
חישוב המידות באמצעות measure()
אחרי שמגדירים כמה סימני זמן, אפשר לבדוק את הזמן שחלף ביניהם. כדי לעשות את זה, משתמשים ב-method measure().
השיטה measure() מחשבת את הזמן שחלף בין הסימונים, ויכולה גם למדוד את הזמן בין הסימון שלכם לבין כל אחד משמות האירועים המוכרים בממשק PerformanceTiming.
לדוגמה, אפשר לחשב את הזמן שעובר מהשלמת ה-DOM עד לטעינה המלאה של מצב האפליקציה באמצעות קוד כמו:
window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');
כשמפעילים את measure(), הפונקציה שומרת את התוצאה בלי קשר לסימונים שהגדרתם, כדי שתוכלו לאחזר אותם מאוחר יותר. אם שומרים את הזמנים בזמן שהאפליקציה פועלת, היא ממשיכה להגיב, ואפשר להוציא את כל הנתונים אחרי שהאפליקציה מסיימת את העבודה כדי לנתח אותם מאוחר יותר.
מחיקת הציונים של clearMarks()
לפעמים כדאי להיפטר מכמה סימונים שהגדרתם. לדוגמה, יכול להיות שאתם מפעילים אצוות באפליקציית האינטרנט שלכם, ואתם רוצים להתחיל מחדש בכל הפעלה.
כדי להסיר את הסימונים שהגדרתם, פשוט קוראים לפונקציה clearMarks().
לכן, הקוד לדוגמה שבהמשך ימחק את כל הסימונים הקיימים, כדי שתוכלו להגדיר שוב את הרצת התזמון אם תרצו.
window.performance.clearMarks();
כמובן שיש מקרים שבהם לא תרצו למחוק את כל הסימונים. אז אם רוצים להיפטר מסימניות ספציפיות, אפשר פשוט להעביר את השם של הסימניה שרוצים להסיר. לדוגמה, הקוד הבא:
window.performance.clearMarks('mark_fully_loaded');
מסיר את הסימון שהגדרנו בדוגמה הראשונה, ומשאיר את כל הסימונים האחרים שהגדרנו ללא שינוי.
יכול להיות שתרצו גם להיפטר מהמדידות שביצעתם, ויש שיטה מתאימה לעשות זאת שנקראת clearMeasures(). הוא פועל בדיוק כמו clearMarks(), אבל הוא פועל על כל המדידות שביצעתם. לדוגמה, הקוד:
window.performance.clearMeasures('measure_load_from_dom');
תסיר את המדד שיצרנו בדוגמה של measure() למעלה. אם רוצים להסיר את כל המדידות, זה עובד בדיוק כמו clearMarks() – פשוט קוראים ל-clearMeasures() בלי ארגומנטים.
איך מקבלים את נתוני התזמון
זה נחמד להגדיר סימנים ולמדוד מרווחים, אבל בשלב מסוים תרצו להגיע לנתוני התזמון האלה כדי לבצע ניתוח כלשהו. גם זה ממש פשוט, כל מה שצריך לעשות הוא להשתמש בממשק של PerformanceTimeline.
לדוגמה, השיטה getEntriesByType() מאפשרת לנו לקבל את כל זמני הסימון או את כל זמני המדידה כרשימה, כדי שנוכל לעבור עליה ולעבד את הנתונים. היתרון הוא שהרשימה מוחזרת בסדר כרונולוגי, כך שאפשר לראות את הסימונים בסדר שבו הם הופעלו באפליקציית האינטרנט.
הקוד הבא:
var items = window.performance.getEntriesByType('mark');
מחזירה לנו רשימה של כל הסימונים שבוצעו באפליקציית האינטרנט שלנו, ואילו הקוד:
var items = window.performance.getEntriesByType('measure');
מחזירה לנו רשימה של כל המדדים שיצרנו.
אתם יכולים גם לקבל רשימה של רשומות לפי השם הספציפי שנתתם להן. לדוגמה, הקוד:
var items = window.performance.getEntriesByName('mark_fully_loaded');
תחזיר לנו רשימה עם פריט אחד שמכיל את חותמת הזמן mark_fully_loaded במאפיין startTime.
תזמון של בקשת XHR (דוגמה)
עכשיו, כשיש לנו תמונה די טובה של User Timing API, אנחנו יכולים להשתמש בו כדי לנתח כמה זמן נמשכות כל בקשות ה-XMLHttpRequests באפליקציית האינטרנט שלנו.
קודם נשנה את כל הבקשות של send() כדי להנפיק קריאה להפעלת פונקציה שמגדירה את הסימונים, ובמקביל נשנה את פונקציות ה-callback של ההצלחה באמצעות קריאה להפעלת פונקציה שמגדירה סימון אחר ואז יוצרת מדד של משך הזמן שלקח לבקשה.
בדרך כלל, ה-XMLHttpRequest שלנו ייראה בערך כך:
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
do_something(e.responseText);
}
myReq.send();
בדוגמה שלנו נוסיף מונה גלובלי כדי לעקוב אחרי מספר הבקשות, וגם כדי להשתמש בו לאחסון מדד לכל בקשה שנשלחת. הקוד לביצוע הפעולה הזו נראה כך:
var reqCnt = 0;
var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
window.performance.mark('mark_end_xhr');
reqCnt++;
window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();
הקוד שלמעלה יוצר מדידה עם ערך שם ייחודי לכל XMLHttpRequest שאנחנו שולחים. אנחנו מניחים שהבקשות פועלות ברצף – הקוד לבקשות מקבילות צריך להיות קצת יותר מורכב כדי לטפל בבקשות שמוחזרות לא לפי הסדר, נשאיר את זה כתרגיל לקורא.
אחרי שאפליקציית האינטרנט תבצע כמה בקשות, נוכל להעביר את כולן למסוף באמצעות הקוד הבא:
var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
var req = items[i];
console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}
סיכום
User Timing API מספק הרבה כלים מצוינים שאפשר להחיל על כל היבט של אפליקציית האינטרנט. כדי לצמצם את הנקודות החמות באפליקציה, אפשר פשוט לפזר קריאות ל-API ברחבי אפליקציית האינטרנט ולעבד את נתוני התזמון שנוצרו כדי לקבל תמונה ברורה של המקומות שבהם מושקע זמן. אבל מה אם הדפדפן שלכם לא תומך ב-API הזה? אין בעיה, כאן יש polyfill מצוין שמדמה את ה-API בצורה טובה מאוד ופועל היטב גם עם webpagetest.org. אז למה אתה מחכה? נסו את User Timing API באפליקציות שלכם כבר עכשיו, תבינו איך להאיץ אותן והמשתמשים שלכם יודו לכם על כך ששיפרתם את החוויה שלהם.