הוספת אינטראקטיביות עם 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) ואפילו לעקוף את מאפיין סגנון התצוגה המחושב מ'ללא'. ל-"inline". עכשיו בדף שלנו מוצג הכיתוב 'שלום לתלמידים אינטראקטיביים!'.

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

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

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

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

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

רוצים לנסות?

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

משוב