API vòng đời trang

Browser Support

  • Chrome: 68.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Ngày nay, đôi khi các trình duyệt hiện đại sẽ tạm ngưng hoặc loại bỏ hoàn toàn các trang khi tài nguyên hệ thống bị hạn chế. Trong tương lai, các trình duyệt muốn chủ động thực hiện việc này để tiêu thụ ít điện năng và bộ nhớ hơn. Page Lifecycle API cung cấp các lệnh gọi lại vòng đời để các trang của bạn có thể xử lý an toàn những biện pháp can thiệp này của trình duyệt mà không ảnh hưởng đến trải nghiệm người dùng. Hãy xem xét API để biết liệu bạn có nên triển khai các tính năng này trong ứng dụng của mình hay không.

Thông tin khái quát

Vòng đời ứng dụng là một cách chính để các hệ điều hành hiện đại quản lý tài nguyên. Trên Android, iOS và các phiên bản Windows gần đây, hệ điều hành có thể khởi động và dừng ứng dụng bất cứ lúc nào. Điều này cho phép các nền tảng này tinh giản và phân bổ lại tài nguyên ở những nơi mang lại lợi ích tốt nhất cho người dùng.

Trên web, trước đây không có vòng đời như vậy và các ứng dụng có thể hoạt động vô thời hạn. Khi chạy nhiều trang web, các tài nguyên quan trọng của hệ thống như bộ nhớ, CPU, pin và mạng có thể bị sử dụng quá mức, dẫn đến trải nghiệm người dùng cuối kém.

Mặc dù nền tảng web từ lâu đã có các sự kiện liên quan đến trạng thái vòng đời (chẳng hạn như load, unloadvisibilitychange), nhưng các sự kiện này chỉ cho phép nhà phát triển phản hồi các thay đổi về trạng thái vòng đời do người dùng bắt đầu. Để web hoạt động một cách đáng tin cậy trên các thiết bị có công suất thấp (và nói chung là tiết kiệm tài nguyên hơn trên tất cả các nền tảng), các trình duyệt cần có cách chủ động thu hồi và phân bổ lại tài nguyên hệ thống.

Trên thực tế, các trình duyệt hiện nay đã áp dụng các biện pháp chủ động để tiết kiệm tài nguyên cho các trang trong thẻ nền và nhiều trình duyệt (đặc biệt là Chrome) muốn làm nhiều hơn thế này để giảm mức sử dụng tài nguyên tổng thể.

Vấn đề là nhà phát triển không có cách nào để chuẩn bị cho những loại biện pháp can thiệp do hệ thống khởi tạo này, hoặc thậm chí không biết rằng chúng đang xảy ra. Điều này có nghĩa là các trình duyệt cần phải thận trọng hoặc có nguy cơ làm hỏng các trang web.

Page Lifecycle API cố gắng giải quyết vấn đề này bằng cách:

  • Giới thiệu và chuẩn hoá khái niệm về các trạng thái vòng đời trên web.
  • Xác định các trạng thái mới do hệ thống khởi tạo, cho phép trình duyệt giới hạn các tài nguyên mà thẻ ẩn hoặc không hoạt động có thể sử dụng.
  • Tạo các API và sự kiện mới cho phép nhà phát triển web phản hồi các quá trình chuyển đổi đến và đi từ những trạng thái mới do hệ thống khởi tạo này.

Giải pháp này mang đến khả năng dự đoán mà các nhà phát triển web cần để tạo các ứng dụng có khả năng chống lại sự can thiệp của hệ thống, đồng thời cho phép trình duyệt tối ưu hoá tài nguyên hệ thống một cách mạnh mẽ hơn, từ đó mang lại lợi ích cho tất cả người dùng web.

Phần còn lại của bài đăng này sẽ giới thiệu các tính năng mới của Vòng đời trang và khám phá mối quan hệ giữa các tính năng này với tất cả các trạng thái và sự kiện hiện có của nền tảng web. Tài liệu này cũng sẽ đưa ra các đề xuất và phương pháp hay nhất cho những loại công việc mà nhà phát triển nên (và không nên) thực hiện ở mỗi trạng thái.

Tổng quan về các trạng thái và sự kiện trong Vòng đời trang

Tất cả các trạng thái Vòng đời trang đều riêng biệt và loại trừ lẫn nhau, nghĩa là một trang chỉ có thể ở một trạng thái tại một thời điểm. Và hầu hết các thay đổi đối với trạng thái vòng đời của một trang thường có thể quan sát được thông qua các sự kiện DOM (xem đề xuất của nhà phát triển cho từng trạng thái để biết các trường hợp ngoại lệ).

Có lẽ cách dễ nhất để giải thích các trạng thái Vòng đời trang (cũng như các sự kiện báo hiệu quá trình chuyển đổi giữa các trạng thái đó) là dùng sơ đồ:

Hình ảnh minh hoạ trạng thái và luồng sự kiện được mô tả trong tài liệu này.
Luồng trạng thái và sự kiện của Page Lifecycle API.

Tiểu bang

Bảng sau đây giải thích chi tiết từng trạng thái. Tài liệu này cũng liệt kê các trạng thái có thể xuất hiện trước và sau, cũng như các sự kiện mà nhà phát triển có thể dùng để theo dõi các thay đổi.

Tiểu bang Mô tả
Đang hoạt động

Một trang ở trạng thái đang hoạt động nếu trang đó hiển thị và có tiêu điểm nhập.

Các trạng thái có thể có trước đó:
passive (thông qua sự kiện focus)
frozen (thông qua sự kiện resume, sau đó là sự kiện pageshow)

Các trạng thái tiếp theo có thể có:
passive (thông qua sự kiện blur)

Thụ động

Một trang ở trạng thái thụ động nếu trang đó hiển thị và không có quyền phát đầu vào.

Các trạng thái có thể có trước đó:
active (thông qua sự kiện blur)
hidden (thông qua sự kiện visibilitychange)
frozen (thông qua sự kiện resume, sau đó là sự kiện pageshow)

Các trạng thái có thể có tiếp theo:
active (thông qua sự kiện focus)
hidden (thông qua sự kiện visibilitychange)

Bị ẩn

Một trang ở trạng thái ẩn nếu không hiển thị (và chưa bị đóng băng, loại bỏ hoặc chấm dứt).

Các trạng thái có thể có trước đó:
passive (thông qua sự kiện visibilitychange)
frozen (thông qua sự kiện resume, sau đó là sự kiện pageshow)

Các trạng thái có thể tiếp theo:
passive (thông qua sự kiện visibilitychange)
frozen (thông qua sự kiện freeze)
discarded (không có sự kiện nào được kích hoạt)
terminated (không có sự kiện nào được kích hoạt)

Bị treo

Ở trạng thái đóng băng, trình duyệt sẽ tạm ngưng việc thực thi các tác vụ có thể đóng băng trong hàng đợi tác vụ của trang cho đến khi trang được mở băng. Điều này có nghĩa là những thứ như bộ hẹn giờ JavaScript và lệnh gọi lại tìm nạp sẽ không chạy. Các tác vụ đang chạy có thể hoàn tất (quan trọng nhất là lệnh gọi lại freeze), nhưng có thể bị giới hạn về những việc có thể làm và thời gian có thể chạy.

Trình duyệt đóng băng các trang để duy trì mức sử dụng CPU/pin/dữ liệu; trình duyệt cũng làm như vậy để cho phép thao tác điều hướng tiến/lùi nhanh hơn – tránh phải tải lại toàn bộ trang.

Các trạng thái có thể có trước đó:
hidden (thông qua sự kiện freeze)

Các trạng thái có thể có tiếp theo:
active (thông qua sự kiện resume, sau đó là sự kiện pageshow)
passive (thông qua sự kiện resume, sau đó là sự kiện pageshow)
hidden (thông qua sự kiện resume)
discarded (không có sự kiện nào được kích hoạt)

Đã chấm dứt

Một trang sẽ ở trạng thái đã kết thúc sau khi trình duyệt bắt đầu huỷ tải và xoá trang đó khỏi bộ nhớ. Không có tác vụ mới nào có thể bắt đầu ở trạng thái này và các tác vụ đang diễn ra có thể bị huỷ nếu chạy quá lâu.

Các trạng thái có thể có trước đó:
hidden (thông qua sự kiện pagehide)

Các trạng thái tiếp theo có thể có:
NONE

Bị loại bỏ

Một trang ở trạng thái bị loại bỏ khi trình duyệt huỷ tải trang đó để tiết kiệm tài nguyên. Không có tác vụ, lệnh gọi lại sự kiện hoặc JavaScript thuộc bất kỳ loại nào có thể chạy ở trạng thái này, vì các thao tác loại bỏ thường xảy ra trong điều kiện hạn chế về tài nguyên, trong đó không thể bắt đầu các quy trình mới.

Ở trạng thái đã loại bỏ, bản thân thẻ (bao gồm cả tiêu đề thẻ và biểu tượng trang web) thường hiển thị cho người dùng ngay cả khi trang đã biến mất.

Các trạng thái có thể có trước đó:
hidden (không có sự kiện nào được kích hoạt)
frozen (không có sự kiện nào được kích hoạt)

Các trạng thái tiếp theo có thể có:
NONE

Sự kiện

Trình duyệt gửi nhiều sự kiện, nhưng chỉ một phần nhỏ trong số đó báo hiệu một thay đổi có thể xảy ra trong trạng thái Vòng đời trang. Bảng sau đây trình bày tất cả các sự kiện liên quan đến vòng đời và liệt kê những trạng thái mà các sự kiện đó có thể chuyển đổi đến và đi.

Tên Thông tin chi tiết
focus

Một phần tử DOM đã nhận được tiêu điểm.

Lưu ý: sự kiện focus không nhất thiết báo hiệu sự thay đổi trạng thái. Thao tác này chỉ báo hiệu một thay đổi trạng thái nếu trước đó trang không có tiêu điểm đầu vào.

Các trạng thái trước đó có thể có:
passive

Các trạng thái hiện tại có thể có:
active

blur

Một phần tử DOM đã mất tiêu điểm.

Lưu ý: sự kiện blur không nhất thiết báo hiệu sự thay đổi trạng thái. Sự kiện này chỉ báo hiệu một thay đổi về trạng thái nếu trang không còn tiêu điểm nhập (tức là trang không chỉ chuyển tiêu điểm từ một phần tử sang một phần tử khác).

Các trạng thái trước đó có thể có:
đang hoạt động

Các trạng thái hiện tại có thể có:
thụ động

visibilitychange

Giá trị visibilityState của tài liệu đã thay đổi. Điều này có thể xảy ra khi người dùng chuyển đến một trang mới, chuyển đổi thẻ, đóng thẻ, thu nhỏ hoặc đóng trình duyệt hoặc chuyển đổi ứng dụng trên hệ điều hành di động.

Các trạng thái trước đó có thể có:
passive
hidden

Các trạng thái hiện tại có thể có:
passive
hidden

freeze *

Trang vừa bị đóng băng. Mọi tác vụ có thể đóng băng trong hàng đợi tác vụ của trang sẽ không được bắt đầu.

Các trạng thái trước đó có thể có:
hidden

Các trạng thái hiện tại có thể có:
frozen

resume *

Trình duyệt đã tiếp tục một trang bị treo.

Các trạng thái trước đó có thể có:
bị treo

Các trạng thái hiện tại có thể có:
active (nếu theo sau là sự kiện pageshow)
passive (nếu theo sau là sự kiện pageshow)
hidden

pageshow

Một mục nhật ký phiên đang được chuyển đến.

Đây có thể là một lượt tải trang hoàn toàn mới hoặc một trang lấy từ bộ nhớ đệm cho thao tác tiến/lùi. Nếu trang được lấy từ bộ nhớ đệm cho thao tác tiến/lùi, thì thuộc tính persisted của sự kiện là true, nếu không thì thuộc tính này là false.

Các trạng thái có thể có trước đó:
frozen (sự kiện resume cũng sẽ được kích hoạt)

Các trạng thái hiện tại có thể có:
active
passive
hidden

pagehide

Một mục nhật ký phiên đang được chuyển đổi.

Nếu người dùng đang chuyển đến một trang khác và trình duyệt có thể thêm trang hiện tại vào bộ nhớ đệm cho thao tác tiến/lùi để sử dụng lại sau này, thì thuộc tính persisted của sự kiện sẽ là true. Khi true, trang đang chuyển sang trạng thái đóng băng, nếu không thì trang đang chuyển sang trạng thái kết thúc.

Các trạng thái trước đó có thể có:
hidden

Các trạng thái hiện tại có thể có:
frozen (event.persisted là true, freeze event follows)
terminated (event.persisted là false, unload event follows)

beforeunload

Cửa sổ, tài liệu và tài nguyên của tài liệu đó sắp bị huỷ tải. Tài liệu vẫn hiển thị và sự kiện vẫn có thể huỷ tại thời điểm này.

Quan trọng: bạn chỉ nên dùng sự kiện beforeunload để cảnh báo người dùng về những thay đổi chưa lưu. Sau khi bạn lưu những thay đổi đó, sự kiện sẽ bị xoá. Bạn không bao giờ được thêm một cách vô điều kiện vào trang, vì làm như vậy có thể ảnh hưởng đến hiệu suất trong một số trường hợp. Hãy xem phần về các API cũ để biết thông tin chi tiết.

Các trạng thái trước đó có thể có:
hidden

Các trạng thái hiện tại có thể có:
terminated

unload

Trang đang được huỷ tải.

Cảnh báo: bạn không nên sử dụng sự kiện unload vì sự kiện này không đáng tin cậy và có thể làm giảm hiệu suất trong một số trường hợp. Hãy xem phần về các API cũ để biết thêm thông tin chi tiết.

Các trạng thái trước đó có thể có:
hidden

Các trạng thái hiện tại có thể có:
terminated

* Cho biết một sự kiện mới do Page Lifecycle API xác định

Các tính năng mới được thêm vào Chrome 68

Biểu đồ trước cho thấy 2 trạng thái do hệ thống khởi tạo chứ không phải do người dùng khởi tạo: frozen (đóng băng) và discarded (bị loại bỏ). Như đã đề cập trước đó, các trình duyệt hiện nay đôi khi đã đóng băng và loại bỏ các thẻ ẩn (tuỳ theo quyết định của trình duyệt), nhưng nhà phát triển không có cách nào biết được thời điểm điều này xảy ra.

Trong Chrome 68, giờ đây, nhà phát triển có thể quan sát thời điểm một thẻ ẩn bị đóng băng và được rã đông bằng cách theo dõi các sự kiện freezeresume trên document.

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

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

Từ Chrome 68, đối tượng document hiện có một thuộc tính wasDiscarded trên Chrome dành cho máy tính (vấn đề này đang theo dõi việc hỗ trợ Android). Để xác định xem một trang có bị loại bỏ khi ở trong một thẻ ẩn hay không, bạn có thể kiểm tra giá trị của thuộc tính này tại thời điểm tải trang (lưu ý: các trang bị loại bỏ phải được tải lại để sử dụng lại).

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

Để biết lời khuyên về những việc quan trọng cần làm trong các sự kiện freezeresume, cũng như cách xử lý và chuẩn bị cho các trang bị loại bỏ, hãy xem đề xuất của nhà phát triển cho từng trạng thái.

Một số phần tiếp theo sẽ cung cấp thông tin tổng quan về cách các tính năng mới này phù hợp với các trạng thái và sự kiện hiện có của nền tảng web.

Cách quan sát các trạng thái Vòng đời trang trong mã

Trong các trạng thái đang hoạt động, thụ độngbị ẩn, bạn có thể chạy mã JavaScript xác định trạng thái Vòng đời trang hiện tại từ các API nền tảng web hiện có.

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

Mặt khác, bạn chỉ có thể phát hiện các trạng thái đóng băngchấm dứt trong trình nghe sự kiện tương ứng (freezepagehide) khi trạng thái thay đổi.

Cách quan sát các thay đổi về trạng thái

Dựa trên hàm getState() đã xác định trước đó, bạn có thể quan sát tất cả các thay đổi về trạng thái PageLifecycle bằng mã sau.

// 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);

Mã này sẽ thực hiện 3 việc sau:

  • Đặt trạng thái ban đầu bằng hàm getState().
  • Xác định một hàm chấp nhận trạng thái tiếp theo và nếu có thay đổi, hãy ghi lại các thay đổi về trạng thái vào bảng điều khiển.
  • Thêm trình nghe sự kiện ghi lại cho tất cả các sự kiện cần thiết trong vòng đời, lần lượt gọi logStateChange(), truyền vào trạng thái tiếp theo.

Một điều cần lưu ý về mã là tất cả trình nghe sự kiện đều được thêm vào window và tất cả đều truyền {capture: true}. Dưới đây là một vài lý do dẫn đến trường hợp này:

  • Không phải tất cả các sự kiện trong Vòng đời trang đều có cùng mục tiêu. pagehidepageshow được kích hoạt trên window; visibilitychange, freezeresume được kích hoạt trên document, còn focusblur được kích hoạt trên các phần tử DOM tương ứng.
  • Hầu hết các sự kiện này đều không tạo chuỗi bọt, tức là bạn không thể thêm trình nghe sự kiện không ghi nhận vào một phần tử tổ tiên chung và quan sát tất cả các sự kiện đó.
  • Giai đoạn bắt giữ thực thi trước giai đoạn mục tiêu hoặc giai đoạn bong bóng, vì vậy, việc thêm trình nghe ở đó sẽ giúp đảm bảo trình nghe chạy trước khi mã khác có thể huỷ trình nghe.

Đề xuất của nhà phát triển cho từng trạng thái

Là nhà phát triển, bạn cần phải hiểu rõ các trạng thái Vòng đời trang biết cách quan sát các trạng thái đó trong mã vì loại công việc bạn nên (và không nên) thực hiện phần lớn phụ thuộc vào trạng thái của trang.

Ví dụ: rõ ràng là không hợp lý khi hiển thị thông báo tạm thời cho người dùng nếu trang ở trạng thái ẩn. Mặc dù ví dụ này khá rõ ràng, nhưng có những đề xuất khác không rõ ràng đến mức đáng để liệt kê.

Tiểu bang Đề xuất dành cho nhà phát triển
Active

Trạng thái đang hoạt động là thời điểm quan trọng nhất đối với người dùng và do đó, đây cũng là thời điểm quan trọng nhất để trang của bạn phản hồi thông tin đầu vào của người dùng.

Mọi thao tác không liên quan đến giao diện người dùng có thể chặn luồng chính đều phải được giảm mức độ ưu tiên xuống khoảng thời gian rảnh hoặc được chuyển sang một worker trên web.

Passive

Ở trạng thái thụ động, người dùng không tương tác với trang nhưng vẫn có thể xem trang. Điều này có nghĩa là các bản cập nhật và ảnh động trên giao diện người dùng vẫn phải mượt mà, nhưng thời gian xảy ra các bản cập nhật này ít quan trọng hơn.

Khi trang thay đổi từ active (đang hoạt động) thành passive (thụ động), đây là thời điểm thích hợp để duy trì trạng thái ứng dụng chưa lưu.

Hidden

Khi trang thay đổi từ trạng thái thụ động sang bị ẩn, có thể người dùng sẽ không tương tác lại với trang đó cho đến khi trang được tải lại.

Quá trình chuyển đổi sang trạng thái ẩn thường cũng là thay đổi trạng thái cuối cùng mà nhà phát triển có thể quan sát một cách đáng tin cậy (điều này đặc biệt đúng trên thiết bị di động, vì người dùng có thể đóng các thẻ hoặc chính ứng dụng trình duyệt và các sự kiện beforeunload, pagehideunload sẽ không được kích hoạt trong những trường hợp đó).

Điều này có nghĩa là bạn nên coi trạng thái ẩn là điểm kết thúc có thể xảy ra cho phiên của người dùng. Nói cách khác, hãy duy trì mọi trạng thái ứng dụng chưa lưu và gửi mọi dữ liệu phân tích chưa gửi.

Bạn cũng nên ngừng thực hiện các bản cập nhật giao diện người dùng (vì người dùng sẽ không thấy các bản cập nhật này) và bạn nên dừng mọi tác vụ mà người dùng không muốn chạy ở chế độ nền.

Frozen

Ở trạng thái đóng băng, các tác vụ có thể đóng băng trong hàng đợi tác vụ sẽ bị tạm ngưng cho đến khi trang được mở băng trở lại – điều này có thể không bao giờ xảy ra (ví dụ: nếu trang bị loại bỏ).

Điều này có nghĩa là khi trang thay đổi từ trạng thái ẩn sang đóng băng, bạn cần phải dừng mọi bộ hẹn giờ hoặc huỷ mọi kết nối. Nếu bị đóng băng, những thao tác này có thể ảnh hưởng đến các thẻ đang mở khác trong cùng một nguồn gốc hoặc ảnh hưởng đến khả năng của trình duyệt trong việc đưa trang vào bộ nhớ đệm quay lại/chuyển tiếp.

Cụ thể, bạn cần:

  • Đóng tất cả các kết nối IndexedDB đang mở.
  • Đóng các kết nối BroadcastChannel đang mở.
  • Đóng các kết nối WebRTC đang hoạt động.
  • Dừng mọi hoạt động thăm dò mạng hoặc đóng mọi kết nối Web Socket đang mở.
  • Giải phóng mọi Web Locks (Khoá web) đã giữ.

Bạn cũng nên duy trì mọi trạng thái hiển thị động (ví dụ: vị trí cuộn trong chế độ xem danh sách vô hạn) thành sessionStorage (hoặc IndexedDB thông qua commit()) mà bạn muốn khôi phục nếu trang bị loại bỏ và tải lại sau.

Nếu trang chuyển từ trạng thái đóng băng sang trạng thái ẩn, bạn có thể mở lại mọi kết nối đã đóng hoặc khởi động lại mọi hoạt động thăm dò mà bạn đã dừng khi trang bị đóng băng lần đầu.

Terminated

Thông thường, bạn không cần làm gì khi một trang chuyển sang trạng thái đã kết thúc.

Vì các trang bị huỷ tải do hành động của người dùng luôn chuyển qua trạng thái ẩn trước khi chuyển sang trạng thái chấm dứt, nên trạng thái ẩn là nơi cần thực hiện logic kết thúc phiên (ví dụ: duy trì trạng thái ứng dụng và báo cáo cho số liệu phân tích).

Ngoài ra (như đã đề cập trong phần đề xuất cho trạng thái ẩn), các nhà phát triển cần nhận ra rằng quá trình chuyển đổi sang trạng thái đã kết thúc không thể được phát hiện một cách đáng tin cậy trong nhiều trường hợp (đặc biệt là trên thiết bị di động), vì vậy, các nhà phát triển phụ thuộc vào các sự kiện kết thúc (ví dụ: beforeunload, pagehideunload) có thể sẽ mất dữ liệu.

Discarded

Nhà phát triển không thể quan sát trạng thái đã loại bỏ tại thời điểm một trang đang bị loại bỏ. Điều này là do các trang thường bị loại bỏ trong điều kiện hạn chế về tài nguyên và việc giải phóng một trang chỉ để cho phép tập lệnh chạy để phản hồi một sự kiện loại bỏ là điều không thể trong hầu hết các trường hợp.

Do đó, bạn nên chuẩn bị cho trường hợp có thể loại bỏ thay đổi từ hidden (bị ẩn) thành frozen (bị đóng băng), sau đó bạn có thể phản ứng với việc khôi phục một trang bị loại bỏ tại thời gian tải trang bằng cách kiểm tra document.wasDiscarded.

Một lần nữa, vì độ tin cậy và thứ tự của các sự kiện vòng đời không được triển khai nhất quán trong tất cả các trình duyệt, nên cách dễ nhất để làm theo lời khuyên trong bảng là sử dụng PageLifecycle.js.

Các API vòng đời cũ cần tránh

Bạn nên tránh những sự kiện sau nếu có thể.

Sự kiện huỷ tải

Nhiều nhà phát triển coi sự kiện unload là một lệnh gọi lại được đảm bảo và dùng sự kiện này làm tín hiệu kết thúc phiên để lưu trạng thái và gửi dữ liệu phân tích. Tuy nhiên, việc này cực kỳ không đáng tin cậy, đặc biệt là trên thiết bị di động! Sự kiện unload không kích hoạt trong nhiều trường hợp huỷ tải thông thường, bao gồm cả việc đóng một thẻ trong trình chuyển đổi thẻ trên thiết bị di động hoặc đóng ứng dụng trình duyệt trong trình chuyển đổi ứng dụng.

Vì lý do này, bạn nên dựa vào sự kiện visibilitychange để xác định thời điểm kết thúc một phiên và coi trạng thái ẩn là thời gian đáng tin cậy cuối cùng để lưu dữ liệu ứng dụng và người dùng.

Hơn nữa, chỉ cần có trình xử lý sự kiện unload đã đăng ký (thông qua onunload hoặc addEventListener()) là có thể ngăn trình duyệt lưu các trang vào bộ nhớ đệm cho thao tác tiến/lùi để tải nhanh hơn khi tiến và lùi.

Trong tất cả các trình duyệt hiện đại, bạn nên luôn sử dụng sự kiện pagehide để phát hiện các trường hợp có thể huỷ tải trang (còn gọi là trạng thái đã chấm dứt) thay vì sự kiện unload. Nếu cần hỗ trợ Internet Explorer phiên bản 10 trở xuống, bạn nên phát hiện tính năng của sự kiện pagehide và chỉ sử dụng unload nếu trình duyệt không hỗ trợ 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.
});

Sự kiện beforeunload

Sự kiện beforeunload có vấn đề tương tự như sự kiện unload, đó là trước đây, sự hiện diện của sự kiện beforeunload có thể ngăn các trang đủ điều kiện sử dụng bộ nhớ đệm cho thao tác tiến/lùi. Các trình duyệt hiện đại không có hạn chế này. Mặc dù một số trình duyệt, để đề phòng, sẽ không kích hoạt sự kiện beforeunload khi cố gắng đưa một trang vào bộ nhớ đệm lui/tiến, tức là sự kiện này không đáng tin cậy như một tín hiệu kết thúc phiên. Ngoài ra, một số trình duyệt (bao gồm cả Chrome) yêu cầu người dùng tương tác trên trang trước khi cho phép sự kiện beforeunload kích hoạt, điều này ảnh hưởng hơn nữa đến độ tin cậy của sự kiện.

Một điểm khác biệt giữa beforeunloadunload là có những trường hợp sử dụng hợp pháp đối với beforeunload. Ví dụ: khi bạn muốn cảnh báo người dùng rằng họ có các thay đổi chưa lưu và sẽ bị mất nếu tiếp tục huỷ tải trang.

Vì có những lý do chính đáng để sử dụng beforeunload, nên bạn chỉ nên thêm trình nghe beforeunload khi người dùng có các thay đổi chưa lưu, sau đó xoá ngay các trình nghe này sau khi người dùng lưu các thay đổi.

Nói cách khác, đừng làm như sau (vì thao tác này sẽ thêm một trình nghe beforeunload vô điều kiện):

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;
  }
});

Thay vào đó, hãy làm như sau (vì thao tác này chỉ thêm trình nghe beforeunload khi cần và xoá trình nghe đó khi không cần):

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);
});

Câu hỏi thường gặp

Tại sao không có trạng thái "đang tải"?

Page Lifecycle API xác định các trạng thái riêng biệt và loại trừ lẫn nhau. Vì một trang có thể được tải ở trạng thái đang hoạt động, thụ động hoặc ẩn và vì trang đó có thể thay đổi trạng thái (hoặc thậm chí bị chấm dứt) trước khi tải xong, nên trạng thái tải riêng biệt không có ý nghĩa trong mô hình này.

Trang của tôi thực hiện công việc quan trọng khi bị ẩn. Làm cách nào để ngăn trang bị đóng băng hoặc bị loại bỏ?

Có rất nhiều lý do chính đáng khiến các trang web không nên bị đóng băng khi đang chạy ở trạng thái ẩn. Ví dụ rõ ràng nhất là một ứng dụng phát nhạc.

Ngoài ra, cũng có những trường hợp Chrome sẽ gặp rủi ro nếu loại bỏ một trang, chẳng hạn như nếu trang đó chứa một biểu mẫu có thông tin đầu vào chưa được gửi của người dùng hoặc nếu trang đó có trình xử lý beforeunload cảnh báo khi trang đang tải xuống.

Hiện tại, Chrome sẽ thận trọng khi loại bỏ các trang và chỉ làm như vậy khi chắc chắn rằng việc này sẽ không ảnh hưởng đến người dùng. Ví dụ: những trang được quan sát thấy thực hiện bất kỳ thao tác nào sau đây khi ở trạng thái ẩn sẽ không bị loại bỏ trừ phi gặp phải tình trạng hạn chế tài nguyên nghiêm trọng:

  • Đang phát âm thanh
  • Sử dụng WebRTC
  • Cập nhật tiêu đề bảng hoặc biểu tượng trang web
  • Hiện cảnh báo
  • Gửi thông báo đẩy

Để biết danh sách các tính năng hiện tại được dùng để xác định xem một thẻ có thể bị đóng băng hoặc loại bỏ một cách an toàn hay không, hãy xem: Heuristics for Freezing & Discarding (Heuristics để đóng băng và loại bỏ) trong Chrome.

Bộ nhớ đệm cho thao tác tiến/lùi là gì?

Bộ nhớ đệm cho thao tác tiến/lùi là một thuật ngữ dùng để mô tả một tính năng tối ưu hoá hoạt động điều hướng mà một số trình duyệt triển khai để giúp người dùng sử dụng nút quay lại và nút tiến nhanh hơn.

Khi người dùng di chuyển khỏi một trang, các trình duyệt này sẽ đóng băng một phiên bản của trang đó để có thể nhanh chóng tiếp tục trong trường hợp người dùng di chuyển trở lại bằng cách sử dụng nút quay lại hoặc nút tiến. Hãy nhớ rằng việc thêm một trình xử lý sự kiện unload sẽ ngăn chặn việc tối ưu hoá này.

Về mọi mặt, trạng thái đóng băng này có chức năng tương tự như trạng thái đóng băng mà các trình duyệt thực hiện để tiết kiệm CPU/pin; vì lý do đó, trạng thái này được coi là một phần của trạng thái vòng đời đóng băng.

Nếu không thể chạy các API không đồng bộ ở trạng thái bị đóng băng hoặc bị chấm dứt, thì làm cách nào để lưu dữ liệu vào IndexedDB?

Ở trạng thái đóng băng và chấm dứt, các tác vụ có thể đóng băng trong hàng đợi tác vụ của một trang sẽ bị tạm ngưng, tức là không thể sử dụng các API dựa trên lệnh gọi lại và không đồng bộ một cách đáng tin cậy.

Mặc dù hầu hết các API IndexedDB đều dựa trên lệnh gọi lại, nhưng phương thức commit() trên giao diện IDBTransaction cung cấp một cách để bắt đầu quy trình cam kết trên một giao dịch đang hoạt động mà không cần chờ các sự kiện từ các yêu cầu đang chờ xử lý được gửi đi. Điều này cung cấp một cách đáng tin cậy để lưu dữ liệu vào cơ sở dữ liệu IndexedDB trong trình nghe sự kiện freeze hoặc visibilitychange vì quá trình cam kết được chạy ngay lập tức thay vì được xếp hàng trong một tác vụ riêng biệt.

Kiểm thử ứng dụng ở trạng thái đóng băng và bị loại bỏ

Để kiểm thử cách ứng dụng của bạn hoạt động ở trạng thái bị đóng băng và bị loại bỏ, bạn có thể truy cập vào chrome://discards để thực sự đóng băng hoặc loại bỏ bất kỳ thẻ nào đang mở.

Giao diện người dùng loại bỏ của Chrome
Giao diện người dùng Chrome Discards

Điều này giúp bạn đảm bảo trang của mình xử lý chính xác các sự kiện freezeresume cũng như cờ document.wasDiscarded khi các trang được tải lại sau khi bị loại bỏ.

Tóm tắt

Những nhà phát triển muốn tôn trọng tài nguyên hệ thống của thiết bị người dùng nên tạo ứng dụng chú trọng đến các trạng thái Vòng đời trang. Điều quan trọng là các trang web không tiêu thụ quá nhiều tài nguyên hệ thống trong những trường hợp mà người dùng không mong đợi

Càng có nhiều nhà phát triển bắt đầu triển khai Page Lifecycle API mới, thì các trình duyệt càng an toàn hơn khi đóng băng và loại bỏ những trang không được sử dụng. Điều này có nghĩa là trình duyệt sẽ tiêu thụ ít bộ nhớ, CPU, pin và tài nguyên mạng hơn, mang lại lợi ích cho người dùng.