שיפור הביצועים של אפליקציית ה-HTML5

מבוא

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

בכל מקרה, כאשר אתם עובדים על אנימציה, חשוב מאוד שהמשתמשים יראו את האנימציות האלה בצורה חלקה. מה שעלינו להבין הוא שלא ניתן ליצור החלקה באנימציות רק על ידי הגדלת קצב הפריימים לשנייה מעבר לסף קוגניטיבי. לצערי, המוח שלנו חכם יותר מזה. מה שתגלו הוא שקצב אנימציה מדויק של 30 פריימים לשנייה (fps) הוא הרבה יותר טוב מ- 60 FPS, עם כמה מסגרות מעטות באמצע. אנשים שונאים את הפרצות.

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

האסטרטגיה

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

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

דיוק חזותי++ עם HTML5

האצת חומרה

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

ה-GPU יכול להאיץ היבטים אלה של המסמך שלך

  • איחוד פריסה כללית
  • מעברי CSS3
  • המרות תלת-ממדיות של CSS3
  • ציור על קנבס
  • ציור WebGL 3D

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

מה אפשר להאיץ?

האצה של GPU פועלת על ידי העברת משימות מוגדרות וספציפיות היטב לחומרה לשימוש מיוחד. התוכנית הכללית היא שהמסמך מחולק למספר "שכבות", שאינן משתנות להיבטים של הדף המואצים. השכבות האלה מעובדות באמצעות צינור עיבוד הנתונים המסורתי של עיבוד. לאחר מכן ה-GPU משמש להרכב השכבות בדף יחיד תוך החלת ה'אפקטים' שניתן להאיץ במהירות. תוצאה אפשרית היא שלאובייקט שמונפש על המסך לא נדרש 'ממסר' יחיד של הדף בזמן האנימציה.

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

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

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

ל-Chrome יש שני דגלים שימושיים בשורת הפקודה שיכולים לעזור לניפוי באגים בהאצה של GPU:

  1. --show-composited-layer-borders מציג גבול אדום מסביב לרכיבים שעברו שינוי ועיבוד ברמת ה-GPU. שיטה טובה לאימות שהמניפולציות מתבצעות בתוך שכבת ה-GPU.
  2. --show-paint-rects כל השינויים שאינם GPU נצבעים, והפעולה הזו יוצרת גבול בהיר מסביב לכל האזורים שנצבעו מחדש. אפשר לראות את הדפדפן מבצע אופטימיזציה של אזורי צבע בפעולה.

ב-Safari יש דגלים דומים בזמן הריצה המתוארים כאן.

מעברי CSS3

המעברים בין ה-CSS הופכים את האנימציות בסגנון טריוויאליות לכולם, אבל הם גם תכונת ביצועים חכמה. מכיוון שמעבר CSS מנוהל על ידי הדפדפן, ניתן לשפר מאוד את הדיוק של האנימציה שלו, ובמקרים רבים גם להאיץ את החומרה. נכון לעכשיו, ב-WebKit (Chrome, Safari, iOS) יש המרת CSS עם האצת חומרה, אבל היא מגיעה במהירות לדפדפנים ולפלטפורמות אחרים.

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

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

תרגום ב-CSS3

אני בטוח שמצאת בעבר אנימציה של מיקום ה-x/y של רכיב בדף. סביר להניח ששיניתם את המאפיינים השמאליים והמובילים של הסגנון המוטבע. בעזרת טרנספורמציות דו-ממדיות, אנחנו יכולים להשתמש בפונקציונליות translate() כדי לשכפל את ההתנהגות הזו.

אנחנו יכולים לשלב את זה עם אנימציית DOM כדי להשתמש הכי טוב שאפשר

<div style="position:relative; height:120px;" class="hwaccel">

  <div style="padding:5px; width:100px; height:100px; background:papayaWhip;
              position:absolute;" id="box">
  </div>
</div>

<script>
document.querySelector('#box').addEventListener('click', moveIt, false);

function moveIt(evt) {
  var elem = evt.target;

  if (Modernizr.csstransforms && Modernizr.csstransitions) {
    // vendor prefixes omitted here for brevity
    elem.style.transition = 'all 3s ease-out';
    elem.style.transform = 'translateX(600px)';

  } else {
    // if an older browser, fall back to jQuery animate
    jQuery(elem).animate({ 'left': '600px'}, 3000);
  }
}
</script>

אנחנו משתמשים ב- Modernizr כדי להציג תכונות לבדיקה של טרנספורמציות דו-ממדיות ב-CSS ומעברי CSS. אם כן, נשתמש ב-Translate כדי לשנות את המיקום. אם היא מונפשת באמצעות מעבר, יש סיכוי טוב שהדפדפן יוכל להאיץ את המהירות באמצעות חומרה. כדי לתת לדפדפן עוד דחיפה בכיוון הנכון, נשתמש ב"תבליט CSS הקסום" שלמעלה.

אם הדפדפן שלנו פחות מסוגל, נחזור ל-jQuery כדי להעביר את הרכיב. אפשר להשתמש בפלאגין של jQuery Transform polyfill של Louis-Remi Babe כדי שכל התהליך יהיה אוטומטי.

window.requestAnimationFrame

requestAnimationFrame הושק על ידי Mozilla ועבר איטרציה על ידי WebKit במטרה לספק לך ממשק API מקורי להפעלת אנימציות, בין אם הן מבוססות על DOM/CSS ובין אם הן מבוססות על <canvas> או על WebGL. הדפדפן יכול לבצע אופטימיזציה של הנפשות בו-זמנית ביחד לזרימה יחידה ולמחזור צביעה מחדש, דבר שמוביל לאנימציה באיכות גבוהה יותר. לדוגמה, אנימציות מבוססות JS שמסונכרנות עם מעברים ב-CSS או SVG SMIL. בנוסף, אם מפעילים את לולאת האנימציה בכרטיסייה שאינה גלויה, הדפדפן לא ימשיך לפעול. במצב כזה יש פחות שימוש במעבד (CPU), ב-GPU ובזיכרון, ולכן חיי הסוללה ארוכים יותר.

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

יצירת פרופילים

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

יצירת פרופילים ב-JavaScript

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

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

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

פרטים נוספים זמינים במסמכים בנושא כלי הפיתוח של Chrome בנושא יצירת פרופילים.

ה-DOM

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

function drawArray(array) {
  for(var i = 0; i < array.length; i++) {
    document.getElementById('test').innerHTML += array[i]; // No good :(
  }
}

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

טיפים וטריקים

פונקציות אנונימיות

לא קל ליצור פרופיל לפונקציות אנונימיות, מפני שאין להן שם שבעזרתו הן יכולות להופיע ב-profiler. יש שתי דרכים לעקוף את זה:

$('.stuff').each(function() { ... });

יש לכתוב ל:

$('.stuff').each(function workOnStuff() { ... });

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

יצירת פרופילים של פונקציות ארוכות

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

  1. השיטה הנכונה: עליכם להגדיר מחדש את הקוד כך שלא יכלול פונקציות ארוכות.
  2. שיטת קבלת הדברים המרושעים: מוסיפים לקוד הצהרות בפורמט של פונקציות קריאה עצמית. אם תפעלו בזהירות, הפעולה הזו לא תשנה את הסמנטיקה והיא תגרום לחלקים מהפונקציה להופיע כפונקציות בודדות בכלי לניתוח ביצועים (profiler): js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } אל תשכחו להסיר את הפונקציות הנוספות האלה לאחר השלמת הפרופיילינג, או אפילו להשתמש בהן כנקודת התחלה לקידוד מחדש של הקוד.

יצירת פרופיל DOM

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

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

יצירת פרופיל DOM

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

מידע נוסף על תצוגת ציר הזמן כלי חלופי ליצירת פרופילים ב-Internet Explorer הוא DynaTrace Ajax Edition.

אסטרטגיות ליצירת פרופילים

הבחנה בין היבטים שונים

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

  1. זמן ההפעלה (הפעלת ה-profiler, טעינה מחדש של האפליקציה, המתנה עד שהאתחול יושלם, עצירה של יוצר הפרופילים.
  2. לחיצה על לחצן והאנימציה שמופיעה לאחר מכן (התחלת יוצר הפרופיל, לחיצה על הלחצן, המתנה עד לסיום האנימציה, עצירת הכלי לניתוח ביצועים).
יצירת פרופילים של ממשק GUI

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

ממשק פרוגרמטי

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

התחלת תהליך יצירת פרופיל באמצעות:

console.profile()

הפסקת יצירת הפרופילים באמצעות:

console.profileEnd()

יכולת החזרה

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

  1. טיימר לא קשור באפליקציה שלכם, שמופעל בזמן שאתם מודדים משהו אחר.
  2. אספן אשפה עושה את העבודה.
  3. כרטיסייה אחרת בדפדפן שמבצעת עבודה קשה באותו שרשור הפעלה.
  4. תוכנית אחרת במחשב צורכת את המעבד (CPU), ובכך גורמת לאפליקציה להיות איטית יותר.
  5. שינויים פתאומיים בשדה הכבידה של כדור הארץ.

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

למדוד, לשפר, למדוד

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

אסטרטגיות לביצוע אופטימיזציה

צמצום אינטראקציית DOM

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

שמירה במטמון של צומתי DOM

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

לפני:

function getElements() {
  return $('.my-class');
}

אחרי:

var cachedElements;
function getElements() {
  if (cachedElements) {
    return cachedElements;
  }
  cachedElements = $('.my-class');
  return cachedElements;
}

ערכי מאפיינים של מטמון

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

לפני:

setInterval(function() {
  var ele = $('#element');
  var left = parseInt(ele.css('left'), 10);
  ele.css('left', (left + 5) + 'px');
}, 1000 / 30);

אחרי: js var ele = $('#element'); var left = parseInt(ele.css('left'), 10); setInterval(function() { left += 5; ele.css('left', left + 'px'); }, 1000 / 30);

העברת מניפולציית DOM מתוך לולאות

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

לפני:

document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  document.getElementById('target').innerHTML += val;
}

אחרי:

var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');

משיכה וזרימות מחדש

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

  • שלב 1: קריאת ערכי ה-DOM הנדרשים לקוד
  • שלב 2: שינוי ה-DOM

נסו לא לתכנת דפוס כמו:

  • שלב 1: קריאת ערכי DOM
  • שלב 2: שינוי ה-DOM
  • שלב 3: קריאת פרטים נוספים
  • שלב 4: משנים את ה-DOM במקום אחר.

לפני:

function paintSlow() {
  var left1 = $('#thing1').css('left');
  $('#otherThing1').css('left', left);
  var left2 = $('#thing2').css('left');
  $('#otherThing2').css('left', left);
}

אחרי:

function paintFast() {
  var left1 = $('#thing1').css('left');
  var left2 = $('#thing2').css('left');
  $('#otherThing1').css('left', left);
  $('#otherThing2').css('left', left);
}

כדאי ליישם עצה זו לגבי פעולות שמתרחשות בהקשר אחד של ביצוע JavaScript. (למשל, בתוך handler של אירועים, בתוך handler של מרווחים או במהלך טיפול בתגובת ajax).

הפעלת הפונקציה paintSlow() מלמעלה יוצרת את התמונה הזו:

paintSlow()

מעבר להטמעה מהירה יותר מניב את התמונה הבאה:

הטמעה מהירה יותר

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

מידע נוסף: עיבוד: צביעה מחדש, זרימה מחדש/ממסר, עיצוב מחדש מאת סטויאן סטפנוב

ציור חוזר ולולאת האירוע

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

השלכות

  1. אם ההפעלה של מחזורי האנימציה של JavaScript נמשכת יותר מ-1/30 שניות, לא תוכלו ליצור אנימציות חלקות כי הדפדפן לא יעוצב מחדש במהלך ההפעלה של ה-JS. כשאתם מצפים לטפל גם באירועי משתמשים, עליכם לפעול מהר יותר.
  2. לפעמים כדאי לעכב פעולות מסוימות של JavaScript עד למועד מאוחר יותר. למשל, setTimeout(function() { ... }, 0) הפעולה הזו מורה לדפדפן לבצע את הקריאה החוזרת (callback) ברגע שלולאת האירועים שוב מושבתת (בחלק מהדפדפנים ימתינו לפחות 10 אלפיות השנייה). עליכם להיות מודעים לכך שהפעולה הזו תיצור שני מחזורי ביצוע של JavaScript הקרובים מאוד זה לזה בזמן. שתי האפשרויות יכולות לגרום לצביעה מחדש של המסך, וכך להכפיל את הזמן הכולל שנדרש לציור. אם הפעולה הזו מפעילה שני צבעים תלויה בהיוריסטיקה בדפדפן.

גרסה רגילה:

function paintFast() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  $('#otherThing2').css('height', '20px');
}
ציור חוזר ולולאת האירוע

כדאי להוסיף עיכוב:

function paintALittleLater() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  setTimeout(function() {
    $('#otherThing2').css('height', '20px');
  }, 10)
}
עיכוב

הגרסה המתוזמנת מראה שהדפדפן צבוע פעמיים, למרות ששני השינויים בדף הם רק 1/100 מחלק שני.

אתחול עצלני

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

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

לפני: js var things = $('.ele > .other * div.className'); $('#button').click(function() { things.show() });

אחרי: js $('#button').click(function() { $('.ele > .other * div.className').show() });

הענקת גישה לאירועים

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

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

ב-jQuery, אפשר לבטא בקלות את זה כך:

$('#parentNode').delegate('.button', 'click', function() { ... });

מתי לא כדאי להשתמש בהענקת גישה לאירועים

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

בעיות ופתרונות אופייניים

הדברים שאני עושה ב$(document).ready לוקחים הרבה זמן

העצה האישית של מלטה: אף פעם לא לעשות שום דבר ב$(document).ready. נסו לספק את המסמך בצורתו הסופית. בסדר, יש לך הרשאה לרשום פונקציות event listener, אבל רק באמצעות בורר המזהים ו/או שימוש בהענקת גישה לאירועים. באירועים יקרים כמו "mousemove", מעכבים את הרישום עד שיצטרך (אירוע עם עכבר ברכיב הרלוונטי).

ואם אתם ממש צריכים לבצע פעולות, כמו שליחת בקשת Ajax כדי לקבל נתונים ממשיים, ואז תוכלו להציג אנימציה נחמדה. כדאי לכלול את האנימציה כ-URI של נתונים, אם מדובר בקובץ GIF מונפש או בדומה.

מכיוון שהוספתי סרט Flash לדף, הכול ממש איטי

הוספת Flash לדף תמיד תאט מעט את העיבוד, מאחר שיש "משא ומתן" על הפריסה הסופית של החלון בין הדפדפן לבין הפלאגין Flash. כאשר אין אפשרות להימנע לחלוטין מהצבת Flash בדפים שלך, הקפד להגדיר את פרמטר ה-Flash 'wmode' לערך 'window' (ברירת המחדל). פעולה זו תשבית את היכולת לשלב רכיבי HTML ו-Flash מורכבים (לא תוכל לראות רכיב HTML שנמצא מעל סרט ה-Flash וסרט ה-Flash לא יכול להיות שקוף). המצב הזה עלול לגרום לאי נוחות, אבל הוא ישפר משמעותית את הביצועים. לדוגמה, כדאי לבדוק איך האתר youtube.com נמנע מהצבת שכבות מעל לנגן הסרטים הראשי.

אני שומר דברים ב-localStorage, ועכשיו האפליקציה שלי מגממת

כתיבה ל-localStorage היא פעולה סינכרונית שכרוכה בהתחלת הרצה של הדיסק הקשיח. אף פעם לא כדאי לבצע פעולות סינכרוניות "ארוכות טווח" בזמן ביצוע אנימציות. מעבירים את הגישה ל-localStorage למקום בקוד שבו אתם בטוחים שהמשתמש לא פעיל ולא פועלות אנימציות.

הפרופיילינג מצביע על כך שבורר jQuery הוא איטי מאוד

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

אם זה לא עוזר או אם אתם רוצים לפעול במהירות בדפדפנים מודרניים, פעלו לפי ההנחיות הבאות:

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

כל המניפולציות ב-DOM האלה נמשכות זמן רב

קבוצה של הוספות, הסרות ועדכונים של צמתים של DOM עלולה להיות איטית מאוד. בדרך כלל אפשר לשפר את זה על ידי יצירת מחרוזת גדולה של html ושימוש ב-domNode.innerHTML = newHTML להחלפת התוכן הישן. שים לב: זה עלול לפגוע מאוד בתחזוקה ועלולה ליצור קישורי זיכרון ב-IE, אז כדאי להיזהר.

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

כלים

  1. JSPerf – קטעי טקסט קטנים של JavaScript להשוואה
  2. Firebug – ליצירת פרופילים ב-Firefox
  3. הכלים של Google Chrome למפתחים (זמין בתור WebInspector ב-Safari)
  4. DOM Monster – אופטימיזציה של ביצועי DOM
  5. DynaTrace Ajax Edition – גרסה משופרת של פרופיילינג וצבע ב-Internet Explorer

קריאה נוספת

  1. מהירות Google
  2. Paul Ireland ב-jQuery Performance
  3. ביצועי JavaScript קיצוניים (Slide Deck)