הוספת אינטראקטיביות עם 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 בזמן שהוא ממתין שהסקריפט יהיה זמין. הפעולה הזו יכולה לשפר את הביצועים באופן משמעותי.

משוב