הוספת אינטראקטיביות עם JavaScript

Ilya Grigorik
Ilya Grigorik

תאריך פרסום: 31 בדצמבר 2013

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

סיכום

  • JavaScript יכול לשלוח שאילתות ל-DOM ול-CSSOM ולשנות אותם.
  • בלוקים של זמן ריצה של JavaScript ב-CSSOM.
  • JavaScript חוסם את היצירה של DOM, אלא אם הוצהר במפורש שהוא אסינכרוני.

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

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

רוצים לנסות?

  • JavaScript מאפשרת לנו להגיע ל-DOM ולשלוף את ההפניה לצומת ה-span המוסתר. יכול להיות שהצומת לא גלוי בעץ הרינדור, אבל הוא עדיין נמצא ב-DOM. לאחר מכן, כשיש לנו את ההפניה, אנחנו יכולים לשנות את הטקסט שלה (באמצעות ‎ .textContent) ואפילו לשנות את מאפיין סגנון התצוגה המחושב שלה מ-'none' ל-'inline'. עכשיו בדף מוצג הכיתוב שלום תלמידים אינטראקטיביים!.

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

תצוגה מקדימה של דף שעבר רינדור במכשיר נייד.

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

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

קודם כול, חשוב לשים לב שבדוגמה הקודמת הסקריפט שקודד בתוך הדף מופיע קרוב לתחתית הדף. למה? כדאי לנסות את זה בעצמכם, אבל אם נזיז את הסקריפט מעל האלמנט <span>, תבחינו שהסקריפט נכשל ומתלונן על כך שהוא לא מצליח למצוא הפניה לאף אלמנט <span> במסמך. כלומר, הפונקציה getElementsByTagName('span') מחזירה את הערך null. זה ממחיש תכונה חשובה: הסקריפט שלנו מופעל בנקודה המדויקת שבה הוא מוכנס למסמך. כשמנתח ה-HTML נתקל בתג script, הוא משהה את תהליך היצירה של ה-DOM ומעביר את השליטה למנוע JavaScript. אחרי שמנוע JavaScript מסיים לפעול, הדפדפן ממשיך מהמקום שבו הפסיק וממשיך ביצירת ה-DOM.

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

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

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

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

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

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

חסימה על ידי מנתח לעומת JavaScript לא סנכרוני

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

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

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

רוצים לנסות?

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

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

כדי לעשות זאת, מוסיפים את המאפיין async לאלמנט <script>:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

רוצים לנסות?

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

משוב