API vòng đời trang

Hỗ trợ trình duyệt

  • 68
  • 79
  • x
  • x

Đôi khi, các trình duyệt hiện đại ngày nay sẽ tạm ngưng hoặc huỷ các trang hoàn toàn 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 nên sẽ tốn ít pin và bộ nhớ hơn. Page Lifecycle API cung cấp các phương thức nối trong vòng đời để các trang của bạn có thể xử lý các hoạt động can thiệp của trình duyệt này một cách an toàn mà không ảnh hưởng đến trải nghiệm người dùng. Hãy xem 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 của ứng dụng là phương thức quan trọng mà các hệ điều hành hiện đại quản lý tài nguyên. Trên các phiên bản Android, iOS và 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 mang lại lợi ích tốt nhất cho người dùng.

Trước đây, trên web chưa có vòng đời như vậy và các ứng dụng có thể duy trì hoạt động vô thời hạn. Với số lượng lớn các trang web đang chạy, các tài nguyên hệ thống quan trọng như bộ nhớ, CPU, pin và mạng có thể bị đăng ký quá mức, dẫn đến trải nghiệm người dùng cuối không tốt.

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 (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 khởi tạo. Để web hoạt động đáng tin cậy trên các thiết bị công suất thấp (và quan tâm nhiều hơn đến tài nguyên nói chung trên tất cả nền tảng), trình duyệt cần có một 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 đã thực hiện 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 thực hiện nhiều hơn nữa – nhằm giảm thiểu dấu vết 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 các loại biện pháp can thiệp do hệ thống khởi tạo này, hoặc thậm chí là biết được rằng chúng đang diễn 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ơ phá vỡ các trang web.

Page Lifecycle API sẽ 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ề trạng thái vòng đời trên web.
  • Việc xác định các trạng thái mới do hệ thống khởi tạo để cho phép các trình duyệt giới hạn tài nguyên mà các 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 sang và đi từ các trạng thái mới này do hệ thống khởi tạo.

Giải pháp này cung cấp khả năng dự đoán mà nhà phát triển web cần để xây dựng những ứng dụng có khả năng thích ứng với sự can thiệp của hệ thống, đồng thời cho phép các trình duyệt tối ưu hoá tài nguyên hệ thống một cách linh hoạt hơn, sau cùng 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 Vòng đời trang mới, đồng thời khám phá mối liên hệ giữa các tính năng này với tất cả trạng thái và sự kiện hiện có trên nền tảng web. Báo cáo này cũng đưa ra các đề xuất và phương pháp hay nhất về 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 của 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 dành cho nhà phát triển đối với 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 của trang – cũng như các sự kiện chuyển đổi tín hiệu giữa các trạng thái đó – là sử dụng một sơ đồ:

Một bản trình bày trực quan về trạng thái và luồng sự kiện được mô tả trong tài liệu này.
Trạng thái và quy trình sự kiện của API Vòng đời của trang

Tiểu bang

Bảng sau đây giải thích chi tiết từng trạng thái. Tệp này cũng liệt kê các trạng thái có thể xảy ra trước và sau, cũng như các sự kiện mà nhà phát triển có thể sử dụng để quan sát các thay đổi.

Tiểu bang Nội dung mô tả
Đang hoạt động

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

Các trạng thái có thể xảy ra trước đó:
thụ động (thông qua sự kiện focus)
đóng băng (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ó:
thụ động (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ó tâm điểm nhập.

Các trạng thái có thể xảy ra trước đó:
đang hoạt động (thông qua sự kiện blur)
ẩn (thông qua sự kiện visibilitychange)
đóng băng (thông qua sự kiện resume, sau đó đến }pageshow{/2)

Các trạng thái tiếp theo có thể có:
đang hoạt động (thông qua sự kiện focus)
ẩn (thông qua sự kiện visibilitychange)

Ẩn

Một trang sẽ ở 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ể xảy ra trước:
thụ động (thông qua sự kiện visibilitychange)
đóng băng (thông qua sự kiện resume, sau đó là sự kiện pageshow)

Các trạng thái tiếp theo có thể xảy ra:
thụ động (thông qua sự kiện visibilitychange)
đóng băng (thông qua sự kiện freeze)
bị loại bỏ (không có sự kiện nào được kích hoạt)
chấm dứt (không có sự kiện nào được kích hoạt)

Đóng băng

Ở trạng thái bị treo, 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 không bị treo. Điều này có nghĩa là những tính năng như đồng hồ 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ụ đã chạy có thể hoàn tất (quan trọng nhất là lệnh gọi lại freeze), nhưng những tác vụ này có thể bị giới hạn về những việc có thể làm và thời gian chạy.

Các trình duyệt đóng băng các trang như một cách để duy trì mức sử dụng CPU/pin/dữ liệu; chúng cũng thực hiện điều này để thao tác 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 trước đó có thể xảy ra:
ẩn (thông qua sự kiện freeze)

Các trạng thái tiếp theo có thể có:
đang hoạt động (thông qua sự kiện resume, sau đó là pageshow sự kiện)
thụ động pageshowpageshow bị ẩn{/2bị độngkhông{/2


resume

Chấm dứt

Một trang sẽ ở trạng thái chấm dứt sau khi bắt đầu bị trình duyệt huỷ tải và xoá 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ị tắt nếu chạy quá lâu.

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

Các trạng thái tiếp theo có thể xảy ra:
KHÔNG CÓ

Đã loại bỏ

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

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

Các trạng thái có thể xảy ra trước đó:
ẩn (không có sự kiện nào được kích hoạt)
bị treo (không có sự kiện nào được kích hoạt)

Các trạng thái tiếp theo có thể xảy ra:
KHÔNG CÓ

Sự kiện

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

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. Thẻ này chỉ báo hiệu sự thay đổi trạng thái nếu trang trước đó chưa có tâm điểm nhập.

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

Các trạng thái hiện tại có thể xảy ra:
đang hoạt động

blur

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. Thẻ này chỉ báo hiệu sự thay đổi trạng thái nếu trang không còn tâm điểm nhập (tức là trang không chỉ chuyển tâm điểm từ phần tử này sang phần tử khác).

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

Các trạng thái hiện tại có thể xảy ra:
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ỏ/đóng trình duyệt hoặc chuyển đổi ứng dụng trên hệ điều hành của thiết bị di động.

Các trạng thái trước có thể có:
thụ động
ẩn

Các trạng thái hiện tại có thể xảy ra:
thụ động
ẩn

freeze *

Trang này vừa bị treo. 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ể:
ẩn

Các trạng thái hiện tại có thể xảy ra:
bị treo

resume *

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

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

Các trạng thái hiện tại có thể xảy ra:
đang hoạt động (nếu theo sau là sự kiện pageshow)
thụ động (nếu theo sau là sự kiện pageshow)
ẩn

pageshow

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

Đây có thể là một lượt tải trang hoàn toàn mới hoặc một trang được 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 sẽ là true, nếu không, thuộc tính này sẽ là false.

Các trạng thái trước đó có thể xảy ra:
bị treo (sự kiện resume cũng có thể đã kích hoạt)

Các trạng thái hiện tại có thể dùng:
đang hoạt động
thụ động
ẩn

pagehide

Một mục nhật ký phiên đang được truyền từ đó.

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 bị treo, nếu không thì trang sẽ chuyển sang trạng thái đã kết thúc.

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

Các trạng thái hiện tại có thể xảy ra:
bị treo (event.persisted là đúng, sự kiện freeze sau đó)
đã chấm dứt (event.persisted là sai, unload sự kiện tiếp theo)

beforeunload

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

Lưu ý quan trọng: bạn chỉ nên sử dụng sự kiện beforeunload để thông báo cho người dùng về các thay đổi chưa lưu. Sau khi lưu những thay đổi đó, sự kiện sẽ bị xoá. Tuyệt đối không được thêm đoạn mã này vào trang vô điều kiện, 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 API cũ để biết thông tin chi tiết.

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

Các trạng thái hiện tại có thể xảy ra:
đã chấm dứt

unload

Đang huỷ tải trang.

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 API cũ để biết thêm thông tin chi tiết.

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

Các trạng thái hiện tại có thể xảy ra:
đã chấm dứt

* Cho biết một sự kiện mới được xác định bởi API Vòng đời của trang

Đã thêm tính năng mới 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: bị treobị từ chối. Như đã đề cập trước đó, hiện nay, các trình duyệt hiện nay thường đóng băng và loại bỏ các thẻ ẩn (tuỳ ý), nhưng nhà phát triển không có cách nào để biết khi nào đ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ị treo và bị treo 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 bao gồm một thuộc tính wasDiscarded trên Chrome dành cho máy tính (tính năng hỗ trợ Android đang được theo dõi trong sự cố này). Để xác định xem một trang có bị loại bỏ khi đang ở trong 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 ý: bạn phải tải lại những trang đã loại bỏ để sử dụng lại).

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

Để được tư vấ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 dành cho nhà phát triển đối với từng trạng thái.

Những phần tiếp theo cung cấp thông tin tổng quan về mức độ phù hợp của những tính năng mới này 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 của trang trong mã

Ở các trạng thái đang hoạt động, thụ độngẩn, bạn có thể chạy mã JavaScript giúp xác định trạng thái Vòng đời của 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, các trạng thái bị treochấm dứt chỉ có thể được phát hiện trong trình nghe sự kiện tương ứng (freezepagehide) khi trạng thái đang thay đổi.

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

Dựa trên hàm getState() được xác định trước đó, bạn có thể quan sát tất cả các thay đổi về trạng thái Vòng đời trang 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 có 3 chức năng:

  • Đặt trạng thái ban đầu bằng hàm getState().
  • Xác định hàm chấp nhận trạng thái tiếp theo và nếu có thay đổi thì ghi nhật ký 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 lưu giữ đối với tất cả các sự kiện cần thiết trong vòng đời, sau đó gọi logStateChange(), chuyển sang trạng thái tiếp theo.

Một 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 sự kiện Vòng đời của trang nào cũng có cùng một 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 không tạo bong bóng trò chuyện, nghĩa là bạn không thể thêm trình nghe sự kiện không chụp ảnh vào một phần tử đối tượng cấp trên chung và quan sát tất cả các sự kiện đó.
  • Giai đoạn ghi lại sẽ thực thi trước các giai đoạn mục tiêu hoặc bong bóng, vì vậy, việc thêm trình nghe vào đó sẽ giúp đảm bảo chúng sẽ chạy trước khi mã khác có thể huỷ chúng.

Đề xuất dành cho nhà phát triển cho từng tiểu bang

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

Ví dụ: việc hiển thị thông báo tạm thời cho người dùng sẽ không hợp lý nếu trang đang ở 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 và không nên liệt kê.

Tiểu bang Đề xuất 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. Do đó, đây cũng là thời điểm quan trọng nhất để trang của bạn đáp ứng hoạt động đầu vào của người dùng.

Mọi tác vụ không phải giao diện người dùng có thể chặn luồng chính đều nên giảm mức độ ưu tiên ở thời gian không hoạt động hoặc giảm tải cho trình chạy web.

Passive

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

Khi trang thay đổi từ đang hoạt động sang 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ừ thụ động sang ẩ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 sang trạng thái ẩn cũng thường là sự thay đổi trạng thái cuối cùng mà các 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, đồng thời các sự kiện beforeunload, pagehideunload 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à có khả năng kết thúc phiên của người dùng. Nói cách khác, hãy duy trì mọi trạng thái chưa được lưu của ứng dụng và gửi mọi dữ liệu phân tích chưa được gửi.

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

Frozen

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

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

Cụ thể, điều quan trọng là bạn phải:

  • Đó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 cuộc thăm dò mạng hoặc đóng mọi kết nối Web Socket đang mở.
  • Huỷ bỏ mọi Khoá web bị tạm giữ.

Bạn cũng nên duy trì mọi trạng thái của chế độ xem động (ví dụ: vị trí cuộn trong chế độ xem danh sách vô hạn) vào sessionStorage (hoặc IndexedDB 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ày.

Nếu trang chuyển đổi từ trạng thái bị treo về trạng thái ẩn, bạn có thể mở lại mọi kết nối đã đóng hoặc bắt đầu lại bất kỳ cuộc thăm dò ý kiến nào mà bạn đã dừng khi trang ban đầu bị treo.

Terminated

Thường thì bạn không cần làm gì khi một trang chuyển đổi sang trạng thái đã chấm dứt.

Vì các trang bị huỷ tải do hành động của người dùng luôn chuyển sang 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 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 Analytics).

Ngoài ra (như đã đề cập trong các đề xuất cho trạng thái ẩn), nhà phát triển rất cần nhận ra rằng trong nhiều trường hợp, nhà phát triển không thể phát hiện một cách đáng tin cậy việc chuyển đổi sang trạng thái đã kết thúc (đặ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 chấm dứt (ví dụ: beforeunload, pagehideunload) có thể bị 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 loại bỏ trang. Nguyên nhân là do các trang thường bị loại bỏ theo các hạn chế về tài nguyên và trong hầu hết các trường hợp, bạn không thể huỷ đóng băng một trang chỉ để cho phép tập lệnh chạy nhằm phản hồi một sự kiện loại bỏ.

Do đó, bạn nên chuẩn bị cho khả năng loại bỏ thay đổi từ ẩn thành bị treo, 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 điểm 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 trong vòng đời không được triển khai một cách nhất quán trên mọi 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 các sự kiện sau đây nếu có thể.

Sự kiện huỷ tải

Nhiều nhà phát triển coi sự kiện unload là lệnh gọi lại được đảm bảo và sử dụng nó như một 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, nhưng việc này rất 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ẻ từ trình chuyển đổi thẻ trên thiết bị di động hoặc đóng ứng dụng trình duyệt từ 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 phiên kết thúc và xem xét trạng thái ẩn là thời gian đáng tin cậy gần nhất để lưu dữ liệu người dùng và ứng dụng.

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

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 lượt huỷ tải trang có thể xảy ra (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 sử dụng tính năng phát hiện 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, do đó, trước đây, sự hiện diện của sự kiện beforeunload có thể khiến các trang không đủ điều kiện 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ù để đề phòng, một số trình duyệt sẽ không kích hoạt sự kiện beforeunload khi cố gắng đưa một trang vào bộ nhớ đệm cho thao tác tiến/lùi. Điều này có nghĩa là sự kiện này không đáng tin cậy để làm 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 thêm đến độ tin cậy của sự kiện đó.

Một điểm khác biệt giữa beforeunloadunload là việc sử dụng hợp pháp 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, họ sẽ mất nếu tiếp tục huỷ tải trang.

Vì có những lý do hợp lệ để sử dụng beforeunload, 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, rồi xoá các thay đổi đó ngay sau khi lưu.

Nói cách khác, đừng làm như vậy (vì thao tác này sẽ thêm trình nghe beforeunload một cách 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.
    return (event.returnValue = true);
  }
});

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

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

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 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 sẽ không hợp lý trong mô hình này.

Trang của tôi vẫn hoạt động quan trọng khi bị ẩn. Làm cách nào để tôi ngăn trang của mình bị treo hoặc bị huỷ?

Có nhiều lý do chính đáng để trang web không nên bị treo khi 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ó những trường hợp Chrome sẽ gặp rủi ro khi loại bỏ trang, chẳng hạn như nếu trang chứa biểu mẫu có thông tin đầu vào của người dùng chưa được gửi hoặc nếu trang có trình xử lý beforeunload cảnh báo khi trang đang huỷ tải.

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ụ: các trang được quan sát thấy là 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 bị hạn chế nghiêm ngặt về tài nguyên:

  • Đ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
  • Đang gửi thông báo đẩy

Để biết các tính năng hiện tại trong danh sách được dùng để xác định xem thẻ có thể được đóng băng hay huỷ một cách an toàn hay không, hãy xem phần: Nguyên tắc thực tế khi đóng băng và huỷ 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ả tính năng tối ưu hoá thao tác mà một số trình duyệt triển khai, giúp sử dụng các nút quay lại và tiến nhanh hơn.

Khi người dùng rời khỏi một trang, các trình duyệt này sẽ cố định 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 quay lại bằng nút quay lại hoặc tiến. Hãy nhớ rằng việc thêm trình xử lý sự kiện unload sẽ ngăn chặn việc tối ưu hoá này.

Đối với mọi ý định và mục đích, hoạt động treo này có chức năng giống như hoạt động của các trình duyệt để tiết kiệm CPU/pin; vì lý do đó, hoạt động này được coi là một phần của trạng thái vòng đời bị treo.

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

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

Trong tương lai, chúng tôi sẽ thêm phương thức commit() vào các đối tượng IDBTransaction, qua đó cung cấp cho nhà phát triển một cách để thực hiện những giao dịch hiệu quả là giao dịch chỉ ghi mà không cần lệnh gọi lại. Nói cách khác, nếu nhà phát triển chỉ ghi dữ liệu vào IndexedDB và không thực hiện một giao dịch phức tạp bao gồm việc đọc và ghi, thì phương thức commit() sẽ có thể hoàn tất trước khi hàng đợi tác vụ bị tạm ngưng (giả sử cơ sở dữ liệu IndexedDB đã mở).

Tuy nhiên, đối với mã cần hoạt động hiện nay, các nhà phát triển có 2 lựa chọn:

  • Sử dụng Bộ nhớ phiên: Bộ nhớ phiên mang tính đồng bộ và được duy trì sau khi huỷ trang.
  • Sử dụng IndexedDB từ trình chạy dịch vụ của bạn: một trình chạy dịch vụ có thể lưu trữ dữ liệu trong IndexedDB sau khi trang bị chấm dứt hoặc bị hủy. Trong trình nghe sự kiện freeze hoặc pagehide, bạn có thể gửi dữ liệu đến trình chạy dịch vụ thông qua postMessage() và trình chạy dịch vụ này có thể xử lý việc lưu dữ liệu.

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

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

Chrome Loại bỏ giao diện người dùng
Giao diện người dùng cho thao tác Loại bỏ Chrome

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

Tóm tắt

Nhà phát triển muốn tôn trọng tài nguyên hệ thống trên thiết bị của người dùng nên lưu ý đến trạng thái Vòng đời của trang khi tạo ứng dụng. Các trang web không được tiêu tốn quá nhiều tài nguyên hệ thống trong những tình huống 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 các Page Lifecycle API mới, thì trình duyệt càng an toàn để đóng băng và loại bỏ các trang không được sử dụng. Điều này có nghĩa là các 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.