ממשק API של מחזור החיים של דפים

תמיכה בדפדפן

  • Chrome: 68.
  • קצה: 79.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

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

רקע

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

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

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

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

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

ממשק ה-API של מחזור החיים של דף מנסה לפתור את הבעיה באמצעות:

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

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

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

סקירה כללית של מצבים ואירועים במחזור החיים של הדף

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

אולי הדרך הקלה ביותר להסביר את המצבים של מחזור החיים של הדף — וגם האירועים שמסמנים מעברים ביניהם – מופיעים בדיאגרמה:

ייצוג חזותי של המצב וזרימת האירוע שמתוארים במסמך הזה.
המצב של ממשק ה-API של מחזור החיים של הדף וזרימת האירועים.

מדינות

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

מדינה תיאור
פעיל

דף נמצא במצב פעיל אם הוא גלוי ויש לו להתמקד בקלט.

מדינות קודמות אפשריות:
פסיבי (באמצעות האירוע focus)
קפוא (באמצעות האירוע resume, ולאחר מכן הסמל pageshow אירוע)

המדינות הבאות האפשריות:
פסיבי (באמצעות האירוע blur)

פסיבית

דף נמצא במצב פסיבי אם הוא גלוי ללא מיקוד בקלט.

מדינות קודמות אפשריות:
פעיל (באמצעות האירוע blur)
מוסתר (דרך visibilitychange אירוע)
קפוא (באמצעות האירוע resume, ולאחר מכן הסמל pageshow אירוע)

המדינות הבאות האפשריות:
פעיל (באמצעות האירוע focus)
מוסתר (דרך visibilitychange אירוע)

סמויה

דף נמצא במצב מוסתר אם הוא לא גלוי (ולא הוקפאו, נמחקו או נסגרו).

מדינות קודמות אפשריות:
פסיבי (דרך visibilitychange אירוע)
קפוא (באמצעות האירוע resume, ולאחר מכן הסמל pageshow אירוע)

המדינות הבאות האפשריות:
פסיבי (דרך visibilitychange אירוע)
קפוא (דרך האירוע freeze)
נמחקה (לא הופעלו אירועים)
נסגרה (לא הופעלו אירועים)

קפוא

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

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

מדינות קודמות אפשריות:
מוסתר (באמצעות האירוע freeze)

המדינות הבאות האפשריות:
פעיל (באמצעות האירוע resume, ולאחר מכן pageshow אירוע)
פסיבי (באמצעות האירוע resume, ולאחר מכן pageshow אירוע)
מוסתרות (באמצעות האירוע resume)
נמחקה (לא הופעלו אירועים)

הסתיים

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

מדינות קודמות אפשריות:
מוסתר (באמצעות האירוע pagehide)

המדינות הבאות האפשריות:
ללא

נמחקה

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

במצב discarded, הכרטיסייה עצמה (כולל כותרת הכרטיסייה וסמל האתר) בדרך כלל גלוי למשתמש למרות שהדף לא קיים יותר.

מדינות קודמות אפשריות:
מוסתר (לא הופעלו אירועים)
קפוא (לא הופעלו אירועים)

המדינות הבאות האפשריות:
ללא

אירועים

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

שם פרטים
focus

רכיב DOM קיבל מיקוד.

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

מדינות קודמות אפשריות:
פסיבי

המדינות הנוכחיות האפשריות:
פעיל

blur

רכיב DOM איבד את המיקוד.

הערה: אירוע blur לא לסמן בהכרח שינוי במדינה. היא מציינת שינוי במדינה רק אם הדף כבר לא מתמקד בקלט (כלומר הדף לא רק הוחלף בדף) מיקוד מרכיב אחד לרכיב אחר).

מדינות קודמות אפשריות:
פעיל

המדינות הנוכחיות האפשריות:
פסיבי

visibilitychange

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

מדינות קודמות אפשריות:
פסיבי
מוסתר

המדינות הנוכחיות האפשריות:
פסיבי
מוסתר

freeze *

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

מדינות קודמות אפשריות:
מוסתר

המדינות הנוכחיות האפשריות:
קפוא

resume *

הדפדפן הפעיל מחדש דף שהוקפא.

מדינות קודמות אפשריות:
קפוא

המדינות הנוכחיות האפשריות:
פעיל (אם מופיע אחריו pageshow אירוע)
פסיבית (אם מופיע אחריו אירוע pageshow)
מוסתר

pageshow

מתבצעת מעבר לרשומה של היסטוריית סשנים.

הטעינה יכולה להיות טעינת דף חדשה לגמרי או דף שנלקח מטמון לדף הקודם/הבא. אם הדף נלקח מהמטמון לדף הקודם/הבא, הנכס persisted הוא true, אחרת הוא false.

מדינות קודמות אפשריות:
קפוא (a resume גם האירוע היה מופעל)

המדינות הנוכחיות האפשריות:
פעיל
פסיבי
מוסתר

pagehide

מתבצעת מעבר מרשומה של היסטוריית סשנים.

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

מדינות קודמות אפשריות:
מוסתר

המדינות הנוכחיות האפשריות:
קפוא (הפרמטר event.persisted נכון, freeze אירוע במעקב)
נסגרה (event.persisted לא נכון, unload אירוע במעקב)

beforeunload

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

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

מדינות קודמות אפשריות:
מוסתר

המדינות הנוכחיות האפשריות:
הסתיים

unload

מתבצעת טעינה של הדף.

אזהרה: אף פעם לא מומלץ להשתמש באירוע unload כי בלתי מהימנים ועלולות לפגוע בביצועים במקרים מסוימים. לצפייה קטע של ממשקי API מדור קודם לקבלת פרטים נוספים.

מדינות קודמות אפשריות:
מוסתר

המדינות הנוכחיות האפשריות:
הסתיים

* מציין אירוע חדש שהוגדר על ידי ה-API של מחזור החיים של הדף

תכונות חדשות שנוספו בגרסה 68 של Chrome

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

ב-Chrome 68, מפתחים יכולים עכשיו לראות מתי כרטיסייה מוסתרת הוקפאה לא נקפאת על ידי האזנה ל-freeze ו-resume אירועים בתאריך document.

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

ב-Chrome 68, האובייקט document כולל עכשיו wasDiscarded נכס ב-Chrome למחשב (מתקיים מעקב אחר תמיכה ב-Android בבעיה הזו). כדי לקבוע אם דף נמחק כשהוא במצב מוסתר ניתן לבדוק את הערך של המאפיין הזה בזמן הטעינה של הדף (הערה: כדי להשתמש בהם שוב, צריך לטעון מחדש את הדפים שנמחקו).

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

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

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

איך להבחין במצבים של מחזור החיים של הדף בקוד

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

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

המצב קפוא וסגור, מצד שני, ניתן לזהות רק ב-event listener המתאים שלהם (freeze ו-pagehide) כי המדינה משתנה.

איך לשים לב לשינויים במצב

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

// Stores the initial state using the `getState()` function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// Options used for all event listeners.
const opts = {capture: true};

// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState()), opts);
});

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
  // In the freeze event, the next state is always frozen.
  logStateChange('frozen');
}, opts);

window.addEventListener('pagehide', (event) => {
  // If the event's persisted property is `true` the page is about
  // to enter the back/forward cache, which is also in the frozen state.
  // If the event's persisted property is not `true` the page is
  // about to be unloaded.
  logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);

הקוד הזה מבצע שלוש פעולות:

  • מגדיר את המצב הראשוני באמצעות הפונקציה getState().
  • מגדירה פונקציה שמקבלת את המצב הבא, ואם יש שינוי, רושם את שינויי המצב במסוף.
  • הוספות צילום פונקציות event listener לכל האירועים הנחוצים במחזור החיים, אשר בתורו מפעילים logStateChange(), עוברים במצב הבא.

דבר אחד שכדאי לדעת לגבי הקוד הוא שכל פונקציות ה-event listener מתווספים אל window וכולם עוברים {capture: true}. יכולות להיות לכך כמה סיבות:

  • לא לכל האירועים במחזור החיים של הדף יש יעד זהה. pagehide, וגם pageshow הופעלו בתאריך window; visibilitychange, freeze וגם resume מופעלים בתאריך document, ו-focus ו-blur מופעלים במכשיר רכיבי ה-DOM המתאימים.
  • רוב האירועים האלה לא מופיעים בבועות, ולכן אי אפשר להוסיף שלא מתעדים את פונקציות event listener לאלמנט אב משותף, ובוחנים את כל מהם.
  • שלב הצילום מתבצע לפני שלבי היעד או הבועה, לכן מאזינים שם מוודאים שהם ירוצו לפני שקוד אחר יכול לבטל אותם.

המלצות למפתחים בכל מדינה

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

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

מדינה המלצות למפתחים
Active

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

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

Passive

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

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

Hidden

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

המעבר אל מוסתר הוא גם שינוי המצב האחרון שנשקף בצורה אמינה על ידי מפתחים (הדבר נכון במיוחד בנייד, מכיוון שהמשתמשים יכולים לסגור כרטיסיות או את אפליקציית הדפדפן עצמה, beforeunload, pagehide וגם unload אירועים לא מופעלים במקרים כאלה).

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

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

Frozen

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

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

חשוב במיוחד:

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

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

Terminated

בדרך כלל אין צורך לבצע פעולה כלשהי כשמעבירים דף למצב סגור.

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

כמו כן (כפי שמצוין בהמלצות עבור על המצב המוסתר), חשוב מאוד למפתחים להבין שהמעבר למצב הסתיים לא יכול להיות מהימן מזוהה במקרים רבים (במיוחד בנייד), לכן מפתחים שתלויים בזמן אירועי סיום (למשל beforeunload, pagehide, ו-unload) מאבדים נתונים.

Discarded

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

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

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

ממשקי API מדור קודם למחזור חיים שיש להימנע מהם

יש להימנע ככל האפשר מהאירועים הבאים.

האירוע 'הסרת הנתונים שנטענו'

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

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

בנוסף, עצם הנוכחות של גורם מטפל באירועים רשום ב-unload (דרך onunload או addEventListener()) יכולים למנוע מדפדפנים כדי לשמור דפים במטמון לדף הקודם/הבא מהר יותר טעינה קדימה ואחורה.

בכל הדפדפנים המתקדמים מומלץ להשתמש תמיד pagehide כדי לזהות העלאות אפשריות של דפים (שנקראת גם מצב הסתיים) ולא באירוע unload. אם צריכים לתמוך ב-Internet Explorer מגרסה 10 ומטה, זיהוי האירוע pagehide ושימוש רק ב-unload אם הדפדפן לא תומך pagehide:

const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';

window.addEventListener(terminationEvent, (event) => {
  // Note: if the browser is able to cache the page, `event.persisted`
  // is `true`, and the state is frozen rather than terminated.
});

האירוע beforeunload

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

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

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

במילים אחרות, אין לעשות זאת (כי הפעולה מוסיפה אוזן beforeunload ללא תנאי):

addEventListener('beforeunload', (event) => {
  // A function that returns `true` if the page has unsaved changes.
  if (pageHasUnsavedChanges()) {
    event.preventDefault();

    // Legacy support for older browsers.
    return (event.returnValue = true);
  }
});

במקום זאת, עשו זאת (מכיוון שהוא מוסיף את ה-listen של beforeunload רק ומסיר אותו כשאין בו צורך):

const beforeUnloadListener = (event) => {
  event.preventDefault();
  
  // Legacy support for older browsers.
  return (event.returnValue = true);
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  removeEventListener('beforeunload', beforeUnloadListener);
});

שאלות נפוצות

למה אין 'טעינה' מדינה?

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

הדף שלי עובד חשוב כשהוא מוסתר. איך אפשר למנוע הקפאה או מחיקה של הדף?

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

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

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

  • מושמע אודיו
  • שימוש ב-WebRTC
  • עדכון הכותרת או סמל האתר של הטבלה
  • הצגת התראות
  • נשלחות התראות

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

מה זה מטמון לדף הקודם/הבא?

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

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

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

אם אי אפשר להריץ ממשקי API אסינכרוניים במצב של 'קפוא' או של 'הסתיים', איך אפשר לשמור את הנתונים ב-IndexedDB?

במצב קפוא או סגור, משימות שניתנות להקפאה בתורי המשימות של דף מושעים, כלומר ממשקי API אסינכרוניים ומבוססי קריאה חוזרת (callback), כמו IndexedDB בלתי מהימן.

בעתיד נוסיף method commit() ל-IDBTransaction אובייקטים, לתת למפתחים דרך לבצע עסקאות מסוג 'כתיבה בלבד' ביעילות שלא מחייבות התקשרות חזרה. במילים אחרות, אם המפתח רק כותב לנתונים של IndexedDB ולא לבצע עסקה מורכבת שכוללת קריאות וכותבים, השיטה commit() תוכל להסתיים לפני שתורי המשימות מושעים (בהנחה שמסד הנתונים של IndexedDB כבר פתוח).

עם זאת, יש למפתחים שתי אפשרויות לקוד שאמור לפעול היום:

  • שימוש באחסון סשנים: אחסון סשנים הוא סינכרוני ונשמר בכל הדפים שנמחקו.
  • משתמשים ב-IndexedDB מ-Service Worker, Service Worker יכול לאחסן נתונים ב- IndexedDB אחרי שהדף נסגר או נמחק. בfreeze או האזנה לאירוע של pagehide אפשר לשלוח נתונים ל-Service Worker באמצעות postMessage(), וה-Service Worker יכול לטפל בשמירת הנתונים.

בדיקת האפליקציה במצב 'קפוא' או 'נמחק'

כדי לבדוק את אופן הפעולה של האפליקציה במצב 'קפוא' או 'נמחק', אפשר לעבור לכתובת chrome://discards כדי להקפיא או למחוק בפועל לפתוח כרטיסיות.

ממשק המשתמש של Chrome Delete
ממשק המשתמש של Chrome DELETEs

כך אפשר לוודא שהדף שלך מטפל כראוי בfreeze ובresume אירועים וגם את הדגל document.wasDiscarded כשדפים נטענים מחדש אחרי זריקת מרץ.

סיכום

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

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