Page Lifecycle API

Browser Support

  • کروم: ۶۸.
  • لبه: ۷۹.
  • فایرفاکس: پشتیبانی نمی‌شود.
  • سافاری: پشتیبانی نمی‌شود.

مرورگرهای مدرن امروزی گاهی اوقات صفحات را به حالت تعلیق در می‌آورند یا وقتی منابع سیستم محدود می‌شوند، آنها را به طور کامل کنار می‌گذارند. در آینده، مرورگرها می‌خواهند این کار را به صورت پیشگیرانه انجام دهند، بنابراین انرژی و حافظه کمتری مصرف می‌کنند. API چرخه حیات صفحه، قلاب‌های چرخه حیات را فراهم می‌کند تا صفحات شما بتوانند با خیال راحت این مداخلات مرورگر را بدون تأثیر بر تجربه کاربر مدیریت کنند. به API نگاهی بیندازید تا ببینید آیا باید این ویژگی‌ها را در برنامه خود پیاده‌سازی کنید یا خیر.

پیشینه

چرخه حیات اپلیکیشن، روشی کلیدی است که سیستم عامل‌های مدرن از طریق آن منابع را مدیریت می‌کنند. در اندروید، iOS و نسخه‌های اخیر ویندوز، اپلیکیشن‌ها می‌توانند در هر زمانی توسط سیستم عامل شروع و متوقف شوند. این امر به این پلتفرم‌ها اجازه می‌دهد تا منابع را ساده‌سازی و مجدداً تخصیص دهند، جایی که به بهترین نحو برای کاربر مفید باشد.

در وب، از نظر تاریخی چنین چرخه عمری وجود نداشته است و برنامه‌ها می‌توانند به طور نامحدود زنده بمانند. با تعداد زیادی از صفحات وب در حال اجرا، منابع حیاتی سیستم مانند حافظه، پردازنده، باتری و شبکه می‌توانند بیش از حد مصرف شوند و منجر به یک تجربه کاربری بد شوند.

اگرچه پلتفرم وب مدت‌هاست که رویدادهایی مربوط به وضعیت چرخه عمر دارد - مانند load ، unload و visibilitychange - اما این رویدادها فقط به توسعه‌دهندگان اجازه می‌دهند تا به تغییرات وضعیت چرخه عمر که توسط کاربر آغاز می‌شود، پاسخ دهند. برای اینکه وب بتواند به طور قابل اعتمادی روی دستگاه‌های کم‌مصرف کار کند (و به طور کلی در همه پلتفرم‌ها از نظر منابع هوشیارتر باشد)، مرورگرها به روشی برای بازیابی و تخصیص مجدد منابع سیستم به صورت پیشگیرانه نیاز دارند.

در واقع، مرورگرهای امروزی اقدامات فعالی را برای صرفه‌جویی در منابع صفحات در تب‌های پس‌زمینه انجام می‌دهند و بسیاری از مرورگرها (به‌ویژه کروم) مایلند این کار را بسیار بیشتر انجام دهند تا ردپای کلی منابع خود را کاهش دهند.

مشکل این است که توسعه‌دهندگان هیچ راهی برای آماده شدن برای این نوع مداخلات سیستمی یا حتی اطلاع از وقوع آنها ندارند. این بدان معناست که مرورگرها باید محتاط باشند یا خطر از کار افتادن صفحات وب را به جان بخرند.

API چرخه حیات صفحه تلاش می‌کند تا این مشکل را با روش‌های زیر حل کند:

  • معرفی و استانداردسازی مفهوم وضعیت‌های چرخه عمر در وب.
  • تعریف حالت‌های جدید و آغاز شده توسط سیستم که به مرورگرها اجازه می‌دهد منابعی را که می‌توانند توسط تب‌های پنهان یا غیرفعال مصرف شوند، محدود کنند.
  • ایجاد APIها و رویدادهای جدید که به توسعه‌دهندگان وب اجازه می‌دهد به انتقال به و از این حالت‌های جدیدِ آغاز شده توسط سیستم پاسخ دهند.

این راه‌حل، پیش‌بینی‌پذیری مورد نیاز توسعه‌دهندگان وب برای ساخت برنامه‌های مقاوم در برابر مداخلات سیستمی را فراهم می‌کند و به مرورگرها اجازه می‌دهد تا منابع سیستم را با شدت بیشتری بهینه‌سازی کنند که در نهایت به نفع همه کاربران وب خواهد بود.

ادامه‌ی این پست به معرفی ویژگی‌های جدید چرخه‌ی حیات صفحه و بررسی ارتباط آن‌ها با تمام وضعیت‌ها و رویدادهای موجود پلتفرم وب خواهد پرداخت. همچنین توصیه‌ها و بهترین شیوه‌ها را برای انواع کارهایی که توسعه‌دهندگان باید (و نباید) در هر وضعیت انجام دهند، ارائه خواهد داد.

مروری بر وضعیت‌ها و رویدادهای چرخه حیات صفحه

تمام وضعیت‌های چرخه حیات صفحه گسسته و متقابلاً منحصر به فرد هستند، به این معنی که یک صفحه فقط می‌تواند در یک زمان در یک وضعیت باشد. و بیشتر تغییرات در وضعیت چرخه حیات یک صفحه معمولاً از طریق رویدادهای DOM قابل مشاهده هستند (برای استثنائات به توصیه‌های توسعه‌دهنده برای هر وضعیت مراجعه کنید).

شاید ساده‌ترین راه برای توضیح وضعیت‌های چرخه حیات صفحه - و همچنین رویدادهایی که انتقال بین آنها را نشان می‌دهند - با یک نمودار باشد:

نمایش بصری از جریان وضعیت و رویداد که در سراسر این سند شرح داده شده است.
جریان وضعیت و رویداد API چرخه حیات صفحه.

ایالت‌ها

جدول زیر هر وضعیت را با جزئیات توضیح می‌دهد. همچنین وضعیت‌های ممکن قبل و بعد از آن و همچنین رویدادهایی که توسعه‌دهندگان می‌توانند برای مشاهده تغییرات استفاده کنند را فهرست می‌کند.

ایالت توضیحات
فعال

یک صفحه در حالت فعال است اگر قابل مشاهده باشد و فوکوس ورودی داشته باشد.

حالت‌های احتمالی قبلی:
غیرفعال (از طریق رویداد focus )
منجمد شده (از طریق رویداد resume ، سپس رویداد pageshow )

حالت‌های احتمالی بعدی:
غیرفعال (از طریق رویداد blur )

منفعل

اگر صفحه‌ای قابل مشاهده باشد و فوکوس ورودی نداشته باشد، در حالت غیرفعال قرار دارد.

حالت‌های احتمالی قبلی:
فعال (از طریق رویداد blur )
پنهان (از طریق رویداد visibilitychange )
منجمد شده (از طریق رویداد resume ، سپس رویداد pageshow )

حالت‌های احتمالی بعدی:
فعال (از طریق رویداد focus )
پنهان (از طریق رویداد visibilitychange )

پنهان

یک صفحه در حالت پنهان است اگر قابل مشاهده نباشد (و مسدود، حذف یا خاتمه نیافته باشد).

حالت‌های احتمالی قبلی:
غیرفعال (از طریق رویداد visibilitychange )
منجمد شده (از طریق رویداد resume ، سپس رویداد pageshow )

حالت‌های احتمالی بعدی:
غیرفعال (از طریق رویداد visibilitychange )
منجمد (از طریق رویداد freeze )
دور انداخته شد (هیچ رویدادی ثبت نشد)
خاتمه یافته (هیچ رویدادی رخ نداده است)

منجمد

در حالت فریز شده، مرورگر اجرای وظایف قابل فریز شدن در صف وظایف صفحه را تا زمانی که صفحه از حالت فریز خارج شود، به حالت تعلیق در می‌آورد. این بدان معناست که مواردی مانند تایمرهای جاوا اسکریپت و فراخوانی‌های واکشی اجرا نمی‌شوند. وظایف در حال اجرا می‌توانند به پایان برسند (مهم‌تر از همه، فراخوانی freeze )، اما ممکن است در کاری که می‌توانند انجام دهند و مدت زمان اجرای آنها محدود باشد.

مرورگرها صفحات را به عنوان راهی برای حفظ مصرف CPU/باتری/داده‌ها متوقف می‌کنند؛ آنها همچنین این کار را به عنوان راهی برای فعال کردن پیمایش سریع‌تر به عقب/جلو انجام می‌دهند - و از نیاز به بارگذاری مجدد کامل صفحه جلوگیری می‌کنند.

حالت‌های احتمالی قبلی:
پنهان (از طریق رویداد freeze )

حالت‌های احتمالی بعدی:
فعال (از طریق رویداد resume ، سپس رویداد pageshow )
غیرفعال (از طریق رویداد resume ، سپس رویداد pageshow )
پنهان (از طریق رویداد resume )
دور انداخته شد (هیچ رویدادی ثبت نشد)

فسخ شده

یک صفحه زمانی در حالت خاتمه یافته قرار می‌گیرد که مرورگر شروع به تخلیه و پاک کردن آن از حافظه کند. در این حالت هیچ وظیفه جدیدی نمی‌تواند شروع شود و وظایف در حال انجام نیز در صورت طولانی شدن زمان اجرا، ممکن است از بین بروند.

حالت‌های احتمالی قبلی:
پنهان (از طریق رویداد pagehide )

حالت‌های احتمالی بعدی:
هیچکدام

دور انداخته شده

یک صفحه زمانی در حالت دور انداخته شده قرار دارد که توسط مرورگر به منظور صرفه‌جویی در منابع، از بارگذاری خارج شود. هیچ وظیفه، فراخوانی رویداد یا جاوا اسکریپتی از هیچ نوعی نمی‌تواند در این حالت اجرا شود، زیرا دور انداختن‌ها معمولاً تحت محدودیت‌های منابع رخ می‌دهند، جایی که شروع فرآیندهای جدید غیرممکن است.

در حالت حذف‌شده ، خود تب (شامل عنوان تب و فاوآیکون) معمولاً برای کاربر قابل مشاهده است، حتی اگر صفحه از بین رفته باشد.

حالت‌های احتمالی قبلی:
پنهان (هیچ رویدادی اجرا نشد)
منجمد (هیچ رویدادی اجرا نشد)

حالت‌های احتمالی بعدی:
هیچکدام

رویدادها

مرورگرها رویدادهای زیادی را ارسال می‌کنند، اما تنها بخش کوچکی از آنها تغییر احتمالی در وضعیت چرخه حیات صفحه را نشان می‌دهند. جدول زیر تمام رویدادهایی را که مربوط به چرخه حیات هستند، شرح می‌دهد و وضعیت‌هایی را که ممکن است به آنها منتقل شوند و از آنها خارج شوند، فهرست می‌کند.

نام جزئیات
focus

یک عنصر DOM فوکوس (focus) دریافت کرده است.

نکته: رویداد focus لزوماً نشان‌دهنده‌ی تغییر وضعیت نیست. این رویداد فقط در صورتی تغییر وضعیت را نشان می‌دهد که صفحه قبلاً دارای ورودی focus نبوده باشد.

حالت‌های احتمالی قبلی:
منفعل

حالت‌های فعلی احتمالی:
فعال

blur

یک عنصر DOM فوکوس را از دست داده است.

نکته: یک رویداد blur لزوماً تغییر وضعیت را نشان نمی‌دهد. این رویداد فقط در صورتی تغییر وضعیت را نشان می‌دهد که صفحه دیگر فوکوس ورودی نداشته باشد (یعنی صفحه فقط فوکوس را از یک عنصر به عنصر دیگر تغییر نداده باشد).

حالت‌های احتمالی قبلی:
فعال

حالت‌های فعلی احتمالی:
منفعل

visibilitychange

مقدار visibilityState سند تغییر کرده است. این اتفاق می‌تواند زمانی رخ دهد که کاربر به صفحه جدیدی می‌رود، تب‌ها را عوض می‌کند، تبی را می‌بندد، مرورگر را کوچک می‌کند یا می‌بندد، یا در سیستم‌عامل‌های موبایل بین برنامه‌ها جابه‌جا می‌شود.

حالت‌های احتمالی قبلی:
منفعل
پنهان

حالت‌های فعلی احتمالی:
منفعل
پنهان

freeze *

صفحه به تازگی مسدود شده است. هیچ وظیفه قابل مسدود شدنی در صف وظایف صفحه شروع نخواهد شد.

حالت‌های احتمالی قبلی:
پنهان

حالت‌های فعلی احتمالی:
یخ زده

resume *

مرورگر صفحه‌ای که هنگ کرده بود را از سر گرفته است.

حالت‌های احتمالی قبلی:
یخ زده

حالت‌های فعلی احتمالی:
فعال (اگر پس از آن رویداد pageshow قرار گیرد)
مجهول (اگر پس از آن رویداد pageshow قرار گیرد)
پنهان

pageshow

یک ورودی تاریخچه جلسه در حال پیمایش است.

این می‌تواند بارگذاری یک صفحه کاملاً جدید یا گرفتن صفحه از حافظه پنهان back/forward باشد. اگر صفحه از حافظه پنهان back/forward گرفته شده باشد، ویژگی persisted مربوط به این رویداد برابر true و در غیر این صورت برابر false است.

حالت‌های احتمالی قبلی:
مسدود شده (یک رویداد resume نیز فعال می‌شد)

حالت‌های فعلی احتمالی:
فعال
منفعل
پنهان

pagehide

یک ورودی تاریخچه جلسه از آن عبور می‌کند.

اگر کاربر در حال پیمایش به صفحه دیگری باشد و مرورگر بتواند صفحه فعلی را به حافظه پنهان back/forward اضافه کند تا بعداً دوباره استفاده شود، ویژگی persisted مربوط به این رویداد برابر true است. وقتی true ، صفحه وارد حالت freeze شده است، در غیر این صورت وارد حالت terminated شده است.

حالت‌های احتمالی قبلی:
پنهان

حالت‌های فعلی احتمالی:
منجمد ( event.persisted مقدار true را دارد، رویداد freeze به دنبال آن می‌آید)
خاتمه یافته ( event.persisted مقدار false دارد، رویداد unload به دنبال آن می‌آید)

beforeunload

پنجره، سند و منابع آن در شرف تخلیه هستند. سند هنوز قابل مشاهده است و رویداد در این مرحله هنوز قابل لغو است.

نکته مهم: رویداد beforeunload فقط باید برای هشدار دادن به کاربر در مورد تغییرات ذخیره نشده استفاده شود. پس از ذخیره این تغییرات، رویداد باید حذف شود. هرگز نباید بدون قید و شرط به صفحه اضافه شود، زیرا انجام این کار در برخی موارد می‌تواند به عملکرد آسیب برساند. برای جزئیات بیشتر به بخش APIهای قدیمی مراجعه کنید.

حالت‌های احتمالی قبلی:
پنهان

حالت‌های فعلی احتمالی:
فسخ شده

unload

صفحه در حال تخلیه است.

هشدار: استفاده از رویداد unload هرگز توصیه نمی‌شود زیرا غیرقابل اعتماد است و در برخی موارد می‌تواند به عملکرد آسیب برساند. برای جزئیات بیشتر به بخش APIهای قدیمی مراجعه کنید.

حالت‌های احتمالی قبلی:
پنهان

حالت‌های فعلی احتمالی:
فسخ شده

* نشان‌دهنده‌ی یک رویداد جدید است که توسط API چرخه‌ی حیات صفحه تعریف شده است.

ویژگی‌های جدید اضافه شده در کروم ۶۸

نمودار قبلی دو حالت را نشان می‌دهد که به جای اینکه توسط کاربر آغاز شوند، توسط سیستم آغاز می‌شوند: مسدود شده و کنار گذاشته شده . همانطور که قبلاً ذکر شد، مرورگرهای امروزی گاهی اوقات تب‌های پنهان را (به صلاحدید خود) مسدود و کنار می‌گذارند، اما توسعه‌دهندگان هیچ راهی برای اطلاع از زمان وقوع این اتفاق ندارند.

در کروم ۶۸، توسعه‌دهندگان اکنون می‌توانند با گوش دادن به رویدادهای freeze و resume در document ، متوجه شوند که یک تب پنهان چه زمانی متوقف و چه زمانی از حالت مسدود خارج می‌شود.

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

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

از کروم ۶۸ به بعد، شیء document object) در کروم دسکتاپ شامل یک ویژگی wasDiscarded می‌شود ( پشتیبانی اندروید در این شماره در حال پیگیری است ). برای تعیین اینکه آیا یک صفحه در یک تب مخفی حذف شده است یا خیر، می‌توانید مقدار این ویژگی را در زمان بارگذاری صفحه بررسی کنید (توجه: صفحات حذف شده باید برای استفاده مجدد دوباره بارگذاری شوند).

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

برای راهنمایی در مورد نکات مهم در رویدادهای freeze و resume ، و همچنین نحوه مدیریت و آماده شدن برای صفحاتی که حذف می‌شوند، به توصیه‌های توسعه‌دهندگان برای هر حالت مراجعه کنید.

بخش‌های بعدی، مروری بر چگونگی تطبیق این ویژگی‌های جدید با وضعیت‌ها و رویدادهای پلتفرم وب موجود ارائه می‌دهند.

نحوه مشاهده وضعیت چرخه حیات صفحه در کد

در حالت‌های فعال ، غیرفعال و پنهان ، می‌توان کد جاوا اسکریپتی را اجرا کرد که حالت فعلی چرخه حیات صفحه را از APIهای پلتفرم وب موجود تعیین می‌کند.

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

از سوی دیگر، حالت‌های مسدود شده و خاتمه یافته ، تنها در شنونده رویداد مربوطه ( 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() وضعیت اولیه را تنظیم می‌کند.
  • تابعی را تعریف می‌کند که حالت بعدی را می‌پذیرد و در صورت وجود تغییر، تغییرات حالت را در کنسول ثبت می‌کند.
  • شنونده‌های رویدادِ ثبت‌کننده را برای تمام رویدادهای چرخه حیات ضروری اضافه می‌کند، که به نوبه خود تابع logStateChange() فراخوانی کرده و حالت بعدی را ارسال می‌کنند.

نکته‌ای که باید در مورد کد به آن توجه کرد این است که همه شنونده‌های رویداد به window اضافه می‌شوند و همه آنها {capture: true} را ارسال می‌کنند. چند دلیل برای این امر وجود دارد:

  • همه رویدادهای چرخه حیات صفحه هدف یکسانی ندارند. pagehide و pageshow روی window اجرا می‌شوند؛ visibilitychange ، freeze و resume روی document اجرا می‌شوند و focus و blur روی عناصر DOM مربوطه‌شان اجرا می‌شوند.
  • بیشتر این رویدادها حبابی نیستند، به این معنی که افزودن شنونده‌های رویداد غیرگیرنده به یک عنصر اجداد مشترک و مشاهده همه آنها غیرممکن است.
  • مرحله ضبط قبل از مراحل هدف یا حباب اجرا می‌شود، بنابراین اضافه کردن شنونده‌ها در آنجا به اطمینان از اجرای آنها قبل از لغو شدن توسط کدهای دیگر کمک می‌کند.

توصیه‌های توسعه‌دهندگان برای هر ایالت

به عنوان توسعه‌دهندگان، درک وضعیت چرخه حیات صفحه و نحوه مشاهده آنها در کد بسیار مهم است، زیرا نوع کاری که باید (و نباید) انجام دهید تا حد زیادی به وضعیتی که صفحه شما در آن قرار دارد بستگی دارد.

برای مثال، اگر صفحه در حالت پنهان باشد، نمایش یک اعلان موقت به کاربر منطقی نیست. اگرچه این مثال کاملاً واضح است، اما توصیه‌های دیگری نیز وجود دارد که چندان واضح نیستند و ارزش برشمردن دارند.

ایالت توصیه‌های توسعه‌دهندگان
Active

حالت فعال ، بحرانی‌ترین زمان برای کاربر و بنابراین مهم‌ترین زمان برای پاسخگویی صفحه شما به ورودی کاربر است.

هر کار غیر رابط کاربری که ممکن است نخ اصلی را مسدود کند، باید از اولویت خارج شده و به دوره‌های بیکاری منتقل شود یا به یک وب ورکر (web worker) منتقل شود .

Passive

در حالت غیرفعال ، کاربر با صفحه تعامل ندارد، اما همچنان می‌تواند آن را ببیند. این بدان معناست که به‌روزرسانی‌ها و انیمیشن‌های رابط کاربری همچنان باید روان باشند، اما زمان‌بندی این به‌روزرسانی‌ها اهمیت کمتری دارد.

وقتی صفحه از حالت فعال به غیرفعال تغییر می‌کند، زمان مناسبی برای ذخیره کردن حالت ذخیره نشده‌ی برنامه است.

Hidden

وقتی صفحه از غیرفعال به پنهان تغییر می‌کند، ممکن است کاربر تا زمان بارگذاری مجدد آن، دیگر با آن تعامل نداشته باشد.

انتقال به حالت پنهان (hide) اغلب آخرین تغییر وضعیتی است که به طور قابل اعتمادی توسط توسعه‌دهندگان قابل مشاهده است (این امر به ویژه در مورد موبایل صادق است، زیرا کاربران می‌توانند تب‌ها یا خود برنامه مرورگر را ببندند و رویدادهای beforeunload ، pagehide و unload در این موارد اجرا نمی‌شوند).

این یعنی شما باید حالت پنهان را به عنوان پایان احتمالی جلسه کاربر در نظر بگیرید. به عبارت دیگر، هر حالت برنامه ذخیره نشده را حفظ کنید و هر داده تحلیلی ارسال نشده را ارسال کنید.

همچنین باید به‌روزرسانی‌های رابط کاربری را متوقف کنید (زیرا کاربر آنها را نمی‌بیند) و باید هر وظیفه‌ای را که کاربر نمی‌خواهد در پس‌زمینه اجرا شود، متوقف کنید.

Frozen

در حالت فریز شده ، وظایف قابل فریز شدن در صف وظایف تا زمانی که صفحه از حالت فریز خارج شود، به حالت تعلیق در می‌آیند - که ممکن است هرگز اتفاق نیفتد (مثلاً اگر صفحه کنار گذاشته شود).

این یعنی وقتی صفحه از حالت مخفی به حالت قفل‌شده تغییر می‌کند، ضروری است که هرگونه تایمر را متوقف کنید یا هرگونه اتصالی را که در صورت قفل‌شدن، می‌تواند بر سایر تب‌های باز در همان مبدا تأثیر بگذارد، یا بر توانایی مرورگر برای قرار دادن صفحه در حافظه پنهان عقب/جلو تأثیر بگذارد، قطع کنید.

به طور خاص، مهم است که شما:

  • تمام اتصالات باز IndexedDB را ببندید.
  • اتصالات باز BroadcastChannel را ببندید.
  • اتصالات فعال WebRTC را ببندید.
  • هرگونه نظرسنجی شبکه را متوقف کنید یا هرگونه اتصال باز سوکت وب را ببندید.
  • هرگونه قفل وبِ نگه‌داشته‌شده را آزاد کنید.

همچنین باید هر وضعیت نمای پویا (مثلاً موقعیت اسکرول در یک نمای لیست نامحدود) را که می‌خواهید در صورت حذف و بارگذاری مجدد صفحه، بازیابی شود، در sessionStorage (یا IndexedDB از طریق commit() ) ذخیره کنید.

اگر صفحه از حالت مسدود شده به حالت پنهان (hidden) تغییر وضعیت داد، می‌توانید هرگونه اتصال بسته شده را دوباره باز کنید یا هرگونه نظرسنجی (polling) را که هنگام مسدود شدن اولیه صفحه متوقف کرده بودید، مجدداً راه‌اندازی کنید.

Terminated

معمولاً وقتی یک صفحه به حالت خاتمه‌یافته منتقل می‌شود، نیازی به انجام هیچ اقدامی ندارید.

از آنجایی که صفحاتی که در نتیجه‌ی عمل کاربر بارگذاری نمی‌شوند، همیشه قبل از ورود به حالت خاتمه‌یافته ، از حالت پنهان عبور می‌کنند، حالت پنهان جایی است که منطق پایان جلسه (مثلاً حفظ حالت برنامه و گزارش به آنالیتیکس) باید انجام شود.

همچنین (همانطور که در توصیه‌های مربوط به حالت پنهان ذکر شد)، برای توسعه‌دهندگان بسیار مهم است که بدانند انتقال به حالت خاتمه‌یافته در بسیاری از موارد (به‌ویژه در موبایل) به طور قابل اعتمادی قابل تشخیص نیست، بنابراین توسعه‌دهندگانی که به رویدادهای خاتمه (مثلاً beforeunload ، pagehide و unload ) وابسته هستند، احتمالاً داده‌ها را از دست می‌دهند.

Discarded

حالت دور انداخته شده توسط توسعه‌دهندگان در زمانی که یک صفحه دور انداخته می‌شود، قابل مشاهده نیست. دلیل این امر آن است که صفحات معمولاً تحت محدودیت‌های منابع دور انداخته می‌شوند و خارج کردن یک صفحه از حالت فریز شده صرفاً برای اجازه دادن به اجرای اسکریپت در پاسخ به رویداد دور انداختن، در اکثر موارد به سادگی امکان‌پذیر نیست.

در نتیجه، شما باید برای احتمال دور انداختن صفحه در تغییر از حالت پنهان به حالت منجمد آماده باشید، و سپس می‌توانید با بررسی document.wasDiscarded به بازیابی صفحه دور انداخته شده در زمان بارگذاری صفحه واکنش نشان دهید.

بار دیگر، از آنجایی که قابلیت اطمینان و ترتیب رویدادهای چرخه عمر به طور مداوم در همه مرورگرها پیاده‌سازی نشده است، ساده‌ترین راه برای پیروی از توصیه‌های جدول، استفاده از PageLifecycle.js است.

APIهای چرخه عمر قدیمی که باید از آنها اجتناب کنید

در صورت امکان باید از رویدادهای زیر اجتناب شود.

رویداد تخلیه بار

بسیاری از توسعه‌دهندگان رویداد unload را به عنوان یک فراخوانی تضمین‌شده در نظر می‌گیرند و از آن به عنوان سیگنال پایان جلسه برای ذخیره وضعیت و ارسال داده‌های تحلیلی استفاده می‌کنند، اما انجام این کار به خصوص در موبایل بسیار غیرقابل اعتماد است! رویداد unload در بسیاری از موقعیت‌های معمول unload، از جمله بستن یک تب از طریق تعویض تب در موبایل یا بستن برنامه مرورگر از طریق تعویض برنامه، اجرا نمی‌شود.

به همین دلیل، همیشه بهتر است برای تعیین زمان پایان یک جلسه به رویداد visibilitychange تکیه کنید و حالت پنهان را آخرین زمان قابل اعتماد برای ذخیره داده‌های برنامه و کاربر در نظر بگیرید.

علاوه بر این، صرف وجود یک کنترل‌کننده رویداد ثبت‌شده unload (از طریق onunload یا addEventListener() ) می‌تواند مانع از این شود که مرورگرها بتوانند صفحات را در حافظه پنهان back/forward برای بارگذاری سریع‌تر back و forward قرار دهند.

در تمام مرورگرهای مدرن، توصیه می‌شود همیشه از رویداد pagehide برای تشخیص تخلیه احتمالی صفحه (معروف به حالت خاتمه یافته ) به جای رویداد unload استفاده کنید. اگر نیاز به پشتیبانی از نسخه‌های 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 می‌تواند مانع از واجد شرایط بودن صفحات برای حافظه پنهان back/forward شود. مرورگرهای مدرن این محدودیت را ندارند. اگرچه برخی از مرورگرها، به عنوان یک اقدام احتیاطی، هنگام تلاش برای قرار دادن یک صفحه در حافظه پنهان back/forward، رویداد beforeunload اجرا نمی‌کنند، به این معنی که این رویداد به عنوان یک سیگنال پایان جلسه قابل اعتماد نیست. علاوه بر این، برخی از مرورگرها (از جمله کروم ) قبل از اجازه دادن به اجرای رویداد beforeunload ، نیاز به تعامل کاربر با صفحه دارند که این امر بر قابلیت اطمینان آن تأثیر بیشتری می‌گذارد.

یکی از تفاوت‌های بین beforeunload و unload این است که کاربردهای مشروعی برای beforeunload وجود دارد. برای مثال، وقتی می‌خواهید به کاربر هشدار دهید که تغییرات ذخیره نشده‌ای دارد، در صورت ادامه‌ی unload کردن صفحه، تغییرات را از دست خواهد داد.

از آنجایی که دلایل معتبری برای استفاده از 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.
    event.returnValue = true;
  }
});

در عوض، این کار را انجام دهید (زیرا فقط در صورت نیاز شنونده beforeunload را اضافه می‌کند و در صورت عدم نیاز آن را حذف می‌کند):

const beforeUnloadListener = (event) => {
  event.preventDefault();

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

// A function that adds a `beforeunload` listener if there are unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

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

سوالات متداول

چرا حالت "بارگذاری" وجود ندارد؟

API چرخه حیات صفحه، وضعیت‌ها را گسسته و متقابلاً منحصر به فرد تعریف می‌کند. از آنجایی که یک صفحه می‌تواند در حالت فعال، غیرفعال یا پنهان بارگذاری شود، و از آنجایی که می‌تواند قبل از اتمام بارگذاری، وضعیت‌ها را تغییر دهد - یا حتی خاتمه یابد - یک وضعیت بارگذاری جداگانه در این الگو منطقی نیست.

صفحه من وقتی پنهان است کارهای مهمی انجام می‌دهد، چگونه می‌توانم از مسدود شدن یا حذف شدن آن جلوگیری کنم؟

دلایل موجه زیادی وجود دارد که چرا صفحات وب نباید هنگام اجرا در حالت پنهان، قفل شوند. بارزترین مثال، برنامه‌ای است که موسیقی پخش می‌کند.

همچنین موقعیت‌هایی وجود دارد که کنار گذاشتن یک صفحه برای کروم خطرناک است، مثلاً اگر حاوی فرمی با ورودی ارسال نشده از کاربر باشد، یا اگر دارای یک کنترل‌کننده beforeunload باشد که هنگام تخلیه صفحه هشدار می‌دهد.

در حال حاضر، کروم در حذف صفحات محتاطانه عمل می‌کند و فقط زمانی این کار را انجام می‌دهد که مطمئن باشد کاربران را تحت تأثیر قرار نمی‌دهد. برای مثال، صفحاتی که مشاهده شده‌اند هر یک از موارد زیر را در حالت پنهان انجام می‌دهند، حذف نخواهند شد، مگر اینکه تحت محدودیت‌های شدید منابع باشند:

  • پخش صدا
  • استفاده از وب‌آرتی‌سی
  • به‌روزرسانی عنوان جدول یا فاوآیکون
  • نمایش هشدارها
  • ارسال اعلان‌های فوری

برای مشاهده‌ی ویژگی‌های فعلی فهرست که برای تعیین اینکه آیا می‌توان یک تب را با خیال راحت فریز یا حذف کرد، استفاده می‌شوند، به بخش «روش‌های ابتکاری برای فریز و حذف در کروم» مراجعه کنید.

حافظه پنهان عقب/جلو چیست؟

حافظه پنهان (cache) برگشت/جلو اصطلاحی است که برای توصیف بهینه‌سازی ناوبری که برخی مرورگرها پیاده‌سازی می‌کنند، به کار می‌رود و استفاده از دکمه‌های برگشت و جلو را سریع‌تر می‌کند.

وقتی کاربری از یک صفحه خارج می‌شود، این مرورگرها نسخه‌ای از آن صفحه را ثابت نگه می‌دارند تا در صورت بازگشت کاربر با استفاده از دکمه‌های عقب یا جلو، بتوان به سرعت آن را از سر گرفت. به یاد داشته باشید که اضافه کردن یک رویداد unload handler مانع از امکان این بهینه‌سازی می‌شود .

از هر نظر، این فریز کردن از نظر عملکردی مشابه فریز کردن مرورگرها برای صرفه‌جویی در مصرف CPU/باتری است؛ به همین دلیل، بخشی از حالت چرخه حیات فریز شده در نظر گرفته می‌شود.

اگر نتوانم APIهای ناهمزمان را در حالت‌های قفل‌شده یا خاتمه‌یافته اجرا کنم، چگونه می‌توانم داده‌ها را در IndexedDB ذخیره کنم؟

در حالت‌های فریز شده و خاتمه یافته، وظایف قابل فریز شدن در صف وظایف یک صفحه به حالت تعلیق در می‌آیند، به این معنی که APIهای ناهمزمان و مبتنی بر فراخوانی برگشتی را نمی‌توان به طور قابل اعتمادی استفاده کرد.

در حالی که اکثر APIهای IndexedDB مبتنی بر فراخوانی مجدد هستند، متد commit() در رابط IDBTransaction راهی برای شروع فرآیند commit در یک تراکنش فعال بدون انتظار برای ارسال رویدادهای درخواست‌های معوقه فراهم می‌کند. این یک روش قابل اعتماد برای ذخیره داده‌ها در یک پایگاه داده IndexedDB در یک شنونده رویداد freeze یا visibilitychange فراهم می‌کند، زیرا commit بلافاصله اجرا می‌شود و در یک وظیفه جداگانه در صف قرار نمی‌گیرد.

تست برنامه در حالت‌های قفل‌شده و کنار گذاشته‌شده

برای آزمایش نحوه رفتار برنامه خود در حالت‌های مسدود شده و حذف شده، می‌توانید به chrome://discards مراجعه کنید تا هر یک از تب‌های باز خود را مسدود یا حذف کنید.

کروم رابط کاربری را کنار می‌گذارد
کروم رابط کاربری را کنار می‌گذارد

این به شما امکان می‌دهد مطمئن شوید که صفحه شما به درستی رویدادهای freeze و resume و همچنین پرچم document.wasDiscarded را هنگام بارگیری مجدد صفحات پس از حذف، مدیریت می‌کند.

خلاصه

توسعه‌دهندگانی که می‌خواهند به منابع سیستم دستگاه‌های کاربران خود احترام بگذارند، باید برنامه‌های خود را با در نظر گرفتن وضعیت چرخه عمر صفحه (Page Lifecycle) بسازند. بسیار مهم است که صفحات وب در موقعیت‌هایی که کاربر انتظار ندارد، منابع سیستم را بیش از حد مصرف نکنند.

هرچه توسعه‌دهندگان بیشتری شروع به پیاده‌سازی APIهای جدید چرخه عمر صفحه کنند، مرورگرها با خیال راحت‌تری می‌توانند صفحاتی را که استفاده نمی‌شوند، مسدود و دور بیندازند. این بدان معناست که مرورگرها حافظه، CPU، باتری و منابع شبکه کمتری مصرف می‌کنند که این یک مزیت برای کاربران است.