API vòng đời trang

Hỗ trợ trình duyệt

  • 68
  • 79
  • x
  • x

Các trình duyệt hiện đại ngày nay đôi khi sẽ tạm ngưng hoặc huỷ chúng 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 sẽ muốn thực hiện việc này chủ động để tiết kiệm pin và bộ nhớ. Page Lifecycle API cung cấp các hook trong vòng đời để các trang của bạn có thể xử lý các 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 để xem 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à một cách quan trọng mà các hệ điều hành hiện đại quản lý của chúng tôi. Trên Android, iOS và các phiên bản Windows gần đây, bạn có thể khởi động ứng dụng và bị ngừng bất kỳ lúc nào bởi hệ điều hành. Nhờ đó, các nền tảng này có thể đơn giản hoá và phân bổ lại các tài nguyên 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 nào như vậy và các ứng dụng có thể được giữ lại tồn tại vô thời hạn. Với số lượng lớn trang web đang chạy, hệ thống quan trọng các tài nguyên 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 kém.

Mặc dù từ lâu, nền tảng web đã có các sự kiện liên quan đến trạng thái vòng đời — như load, unloadvisibilitychange — những 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 một cách đáng tin cậy trên các thiết bị tiết kiệm pin (và chú ý hơn về tài nguyên nói chung trên tất cả nền tảng) thì cần có một cách để chủ động xác nhận lại và phân bổ lại hệ thống của chúng tôi.

Trên thực tế, các trình duyệt hiện nay đã thực hiện những biện pháp chủ động để tiết kiệm tài nguyên cho các trang trong tab nền và nhiều trình duyệt (đặc biệt là Chrome) muốn làm nhiều việc hơn nữa — nhằm giảm bớt dấu ấn tổng thể về nguồn lực của họ.

Vấn đề là nhà phát triển không có cách nào để chuẩn bị cho những loại do hệ thống gây ra hay thậm chí biết rằng các biện pháp đó đang diễn ra. Điều này có nghĩa là trình duyệt cần phải thận trọng hoặc có nguy cơ làm hỏng 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 có thể bị sử dụng bởi các thẻ ẩn hoặc không hoạt độ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 chuyển đổi sang và từ các trạng thái mới do hệ thống khởi tạo.

Giải pháp này cung cấp khả năng dự đoán mà các nhà phát triển web cần để xây dựng các ứng dụng có khả năng chống chọi lại sự can thiệp của hệ thống, đồng thời cho phép trình duyệt tích cực tối ưu hoá tài nguyên hệ thống, mà cuối cùng là 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 cũng như khám phá mối liên hệ giữa chúng với tất cả trạng thái hiện có của nền tảng web và sự kiện. Nền tảng này cũng sẽ đưa ra các đề xuất và phương pháp hay nhất cho các loại công việc mà nhà phát triển nên (và không nên) làm ở 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ả 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 mỗi lần. 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 mỗi 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 chuyển đổi giữa chúng — là bằng một sơ đồ:

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 của Page Lifecycle API và luồng sự kiện.

Tiểu bang

Bảng sau đây giải thích chi tiết về từng trạng thái. Bảng này cũng liệt kê các khả năng 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ể sử dụng để quan sát những thay đổi.

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

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

Những trạng thái có thể trước đó:
thụ động (thông qua sự kiện focus)
bị treo (thông qua sự kiện resume, thì pageshow sự kiện)

Những trạng thái có thể tiếp theo:
thụ động (thông qua sự kiện blur)

Bị động

Một trang sẽ chuyển sang trạng thái thụ động nếu trang đó vẫn hiển thị và có không có tâm điểm nhập.

Những trạng thái có thể trước đó:
đang hoạt động (thông qua sự kiện blur)
bị ẩn (thông qua visibilitychange sự kiện)
bị treo (thông qua sự kiện resume, thì pageshow sự kiện)

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

Đã ẩn

Trang ở trạng thái bị ẩn nếu không hiển thị (và không bị treo, bị loại bỏ hoặc chấm dứt).

Những trạng thái có thể trước đó:
thụ động (thông qua visibilitychange sự kiện)
bị treo (thông qua sự kiện resume, thì pageshow sự kiện)

Những trạng thái có thể tiếp theo:
thụ động (thông qua visibilitychange sự kiện)
bị treo (thông qua sự kiện freeze)
đã 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)

Đã bị treo

Ở trạng thái bị treo, trình duyệt sẽ tạm ngưng thực thi có thể đóng băng nhiệm vụ trong phần hàng đợi công việc cho đến khi trang được bỏ cố định. Điều này có nghĩa là những thứ như Các bộ tính giờ JavaScript và lệnh gọi lại tìm nạp không chạy. Đã chạy công việc có thể hoàn tất (quan trọng nhất là freeze), nhưng có thể họ bị hạn chế về khả năng có thể làm và chúng có thể chạy trong bao lâu.

Trình duyệt cố định các trang như một cách để duy trì việc sử dụng CPU/pin/dữ liệu; chúng cũng là một cách để kích hoạt nhanh hơn thao tác tiến/lùi – tránh phải xem toàn bộ trang tải lại.

Những trạng thái có thể trước đó:
bị ẩn (thông qua sự kiện freeze)

Những trạng thái có thể tiếp theo:
đang hoạt động (thông qua sự kiện resume, sau đó là pageshow sự kiện)
thụ động (thông qua sự kiện resume, sau đó pageshow sự kiện)
bị ẩn (thông qua sự kiện resume)
đã bị loại bỏ (không có sự kiện nào được kích hoạt)

Đã chấm dứt

Trang sẽ ở trạng thái đã chấm dứt sau khi bắt đầu được được trình duyệt huỷ tải và xoá khỏi bộ nhớ. Không các tác vụ mới có thể bắt đầu ở trạng thái này và các tác vụ đang thực hiện có thể bị loại bỏ nếu chạy quá lâu.

Những trạng thái có thể trước đó:
bị ẩn (thông qua sự kiện pagehide)

Những trạng thái có thể tiếp theo:
KHÔNG CÓ

Đã huỷ

Một trang ở trạng thái đã loại bỏ khi bị huỷ tải bằng trình duyệt để 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ũng có thể chạy ở trạng thái này, vì thường bị loại bỏ xảy ra trong những hạn chế về tài nguyên, trong đó việc bắt đầu các quy trình mới là không thể.

Ở trạng thái đã loại bỏ, chính 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.

Những trạng thái có thể trước đó:
bị ẩn (không có sự kiện nào được kích hoạt)
bị đóng băng (không có sự kiện nào được kích hoạt)

Những trạng thái có thể tiếp theo:
KHÔNG CÓ

Sự kiện

Các trình duyệt gửi đi nhiều sự kiện, nhưng chỉ một phần nhỏ trong số đó báo hiệu 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ả sự kiện liên quan đến vòng đời và liệt kê những trạng thái mà chúng có thể chuyển đổi và chuyển sang.

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 là dấu hiệu thay đổi trạng thái. Chỉ báo hiệu sự thay đổi trạng thái nếu trang trước đây không có tiêu điểm nhập.

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

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

blur

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

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

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

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

visibilitychange

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

Những trạng thái có thể trước đó:
thụ động
bị ẩn

Các trạng thái hiện tại có thể áp dụng:
thụ động
bị ẩn

freeze *

Trang này vừa bị đóng băng. Bất kỳ hạng nào có thể đóng băng trong hàng đợi tác vụ của trang sẽ không bắt đầu.

Những trạng thái có thể trước đó:
bị ẩn

Các trạng thái hiện tại có thể áp dụng:
bị đóng băng

resume *

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

Những trạng thái có thể trước đó:
bị đóng băng

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

pageshow

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

Đây có thể là một 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 Thông tin này được lấy từ bộ nhớ đệm cho thao tác tiến/lùi, Thuộc tính persistedtrue, nếu không thì là false.

Những trạng thái có thể trước đó:
bị đóng băng (resume sự kiện nào cũng sẽ kích hoạt)

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

pagehide

Mục nhập nhật ký phiên đang được truyền tải.

Nếu người dùng đang điều hướng đến một trang khác và trình duyệt có thể thêm trang hiện tại để bật tính năng quay lại/chuyển tiếp bộ nhớ đệm để sử dụng lại sau này, thuộc tính persisted của sự kiện là true. Khi true, trang sẽ nhập trạng thái đã được đóng băng, nếu không thì sẽ chuyển sang trạng thái đã chấm dứt.

Những trạng thái có thể trước đó:
bị ẩn

Các trạng thái hiện tại có thể áp dụng:
bị đóng băng (event.persisted là đúng, Sự kiện freeze tiếp theo)
đã chấm dứt (event.persisted là giá trị false, unload sự kiện tiếp theo)

beforeunload

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

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

Những trạng thái có thể trước đó:
bị ẩn

Các trạng thái hiện tại có thể áp dụng:
đã chấm dứt

unload

Trang đang bị huỷ tải.

Cảnh báo: không bao giờ nên sử dụng sự kiện unload vì đó là 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. Xem mục API cũ để biết thêm chi tiết.

Những trạng thái có thể trước đó:
bị ẩn

Các trạng thái hiện tại có thể áp dụng:
đã chấm dứt

* 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 hệ thống khởi tạo do người dùng yêu cầu: bị cố địnhbị loại bỏ. Như đã đề cập trước đó, các trình duyệt hiện nay đôi khi bị treo và huỷ các thẻ ẩn (theo ý mình), nhưng nhà phát triển không có cách nào để biết khi nào điều này đang diễn ra.

Giờ đây, trong Chrome 68, nhà phát triển có thể quan sát thời điểm một thẻ ẩn bị đóng băng và không bị đóng băng bằng cách lắng nghe freezeresume sự kiện vào 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 giờ đây bao gồm một wasDiscarded tài sản trên Chrome dành cho máy tính (chúng tôi đang theo dõi khả năng hỗ trợ của Android trong vấn đề này). Để xác định liệu một trang có bị huỷ khi đang ở trạng thái ẩn hay không , bạn có thể kiểm tra giá trị của thuộc tính này vào thời gian tải trang (lưu ý: các trang đã 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.
}

Để nhận lời khuyên về những việc quan trọng cần làm trong freezeresume sự kiện, 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.

Các phần tiếp theo sẽ cung cấp thông tin tổng quan về cách những tính năng mới này hoạt động 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 trạng thái Vòng đời trang trong mã

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

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

Các trạng thái đã được đóng băngchấm dứt, trên mặt khác, chỉ có thể phát hiện được trong trình nghe sự kiện tương ứng (freezepagehide) theo trạng thái hiện tạ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 Trang Trạng thái vòng đời thay đổi bằng đoạn 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 thực hiện 3 việc:

  • Đặ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, ghi lại các thay đổi về trạng thái vào bảng điều khiển.
  • Thêm quay trình nghe sự kiện cho tất cả các sự kiện cần thiết trong vòng đời, sau đó gọi logStateChange(), truyền ở trạng thái tiếp theo.

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

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

Là nhà phát triển, bạn cần hiểu rõ các trạng thái của Vòng đời trang biết cách quan sát chúng trong mã bởi vì loại công việc bạn nên làm (và nên không) đang 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ị một thông báo tạm thời rõ ràng là không hợp lý cho người dùng nếu trang ở trạng thái ẩn. Mặc dù ví dụ này khá hiển nhiên, có những đề xuất khác không rõ ràng đến mức đáng để liệt kê.

Tiểu bang Đề xuất của nhà phát triển
Active

Trạng thái đang hoạt động là thời gian quan trọng nhất đối với người dùng và do đó thời điểm quan trọng nhất để trang của bạn thích ứng với hoạt động đầu vào của người dùng.

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

Passive

Ở trạng thái thụ động, người dùng không tương tác với trang, nhưng họ vẫn có thể xem nội dung đó. Tức là các bản cập nhật giao diện người dùng và ảnh động vẫn sẽ trơn tru, nhưng thời điểm xảy ra những cập nhật này ít quan trọng hơn.

Khi trang thay đổi từ chủ động sang thụ động, đó là thời điểm thích hợp để lưu giữ trạng thái ứng dụng chưa lưu.

Hidden

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

Quá trình chuyển đổi sang ẩn cũng thường là lần thay đổi trạng thái sau cùng mà nhà phát triển có thể ghi nhận được một cách đáng tin cậy (điều này đặc biệt đúng với 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à beforeunload, pagehideunload sự kiện 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à trạng thái có khả năng kết thúc của người dùng. Nói cách khác, hãy duy trì mọi trạng thái chưa 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 cập nhật giao diện người dùng) bởi người dùng) và bạn nên dừng mọi tác vụ mà người dùng không muốn đang chạy trong nền.

Frozen

Ở trạng thái bị treo, nhiệm 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ị cố định. Đ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ừ bị ẩn thành bị đóng băng bạn cần phải dừng mọi đồng hồ hẹn giờ hoặc phá vỡ những kết nối nếu bị treo, có thể ảnh hưởng đến các thẻ đang mở khác ở cùng nguồn gốc hoặc ảnh hưởng đến khả năng đặt trang vào phần tử bộ nhớ đệm cho thao tác tiến/lùi.

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

Bạn cũng nên duy trì mọi trạng thái xem động (ví dụ: vị trí cuộn ở chế độ xem danh sách vô hạn) để 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ếu trang chuyển từ trạng thái bị treo trở lại bị ẩn, bạn có thể mở lại mọi kết nối đã đóng hoặc bắt đầu lại mọi cuộc thăm dò ý kiến đã dừng khi trang bị đóng băng lần đầu.

Terminated

Thường thì bạn không cần thực hiện bất kỳ hành động nào khi 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 ở trạng thái ẩn trước khi chuyển sang trạng thái đã chấm dứt trạng thái ẩn là nơi 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) là thực hiện.

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

Do đó, bạn nên chuẩn bị cho khả năng bị huỷ trong thay đổi từ ẩn thành bị đóng băng, sau đó bạn có thể phản ứng với việc khôi phục trang bị loại bỏ vào thời gian tải trang bằng cách đang 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 phải là được triển khai một cách nhất quán trong tất cả trình duyệt, 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 nếu có thể.

Sự kiện "unload"

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à sử dụng sự kiện đó như tín hiệu cuối phiên để lưu trạng thái và gửi dữ liệu phân tích, nhưng thực hiệ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 tình huống dỡ hàng thông thường, bao gồm cả việc đóng một tab khỏi tab trình chuyển đổi 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, tốt hơn là bạn nên dựa vào Sự kiện visibilitychange để xác định thời điểm một phiên hoạt động kết thúc và xem xét trạng thái ẩn thời điểm đáng tin cậy cuối cùng để lưu dữ liệu người dùng và ứng dụng.

Hơn nữa, sự hiện diện đơn thuần của trình xử lý sự kiện unload đã đăng ký (thông qua onunload hoặc addEventListener()) có thể khiến trình duyệt không thể để đưa các trang vào bộ nhớ đệm cho thao tác tiến/lùi để nhanh hơn tải lùi và tiế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 số lượt tải trang có thể bị gỡ tải (còn gọi là trạng thái đã chấm dứt) thay vì sự kiện unload. Nếu bạn cần hỗ trợ Internet Explorer phiên bản 10 trở xuống, bạn nên 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, trong đó 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 dùng bộ nhớ đệm cho thao tác tiến/lùi. 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 sẽ không kích hoạt để đề phòng sự kiện beforeunload khi cố gắng đưa một trang vào chế độ tiến/lùi bộ nhớ đệm, tức là sự kiện không đáng tin cậy dưới dạng tín hiệu cuối phiên. Ngoài ra, một số trình duyệt (bao gồm Chrome) yêu cầu sự tương tác của người dùng trên trang trước khi cho phép sự kiện beforeunload kích hoạt, ảnh hưởng nhiều hơn đến độ tin cậy của nó.

Một điểm khác biệt giữa beforeunloadunload là có trường hợp sử dụng hợp pháp beforeunload. Ví dụ: khi bạn muốn cảnh báo người dùng họ sẽ mất các thay đổi chưa lưu 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 nên chỉ thêm trình nghe beforeunload khi người dùng có các thay đổi chưa lưu, sau đó hãy xoá các mật khẩu đó ngay sau khi lưu.

Nói cách khác, không làm việc này (vì tính năng này sẽ thêm 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.
    return (event.returnValue = true);
  }
});

Thay vào đó, hãy làm điều này (vì nó chỉ thêm trình nghe beforeunload khi cần thiết và xoá 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ó nút "tải" trạng thái?

Page Lifecycle API xác định các trạng thái tách biệt và loại trừ lẫn nhau. Vì một trang có thể được tải ở trạng thái chủ động, thụ động hoặc ẩn và vì nó có thể thay đổi trạng thái (hoặc thậm chí bị chấm dứt) trước khi tải xong, 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 hoạt động rất quan trọng khi bị ẩn, làm cách nào để tôi ngăn trang bị đóng băng hoặc bị huỷ?

Có nhiều lý do chính đáng để không đóng băng trang web trong 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, trong một số trường hợp, Chrome có nguy cơ loại bỏ trang, như nếu nó chứa biểu mẫu với thông tin người dùng chưa gửi hoặc nếu nó có Trình xử lý beforeunload cảnh báo khi trang đang bị huỷ tải.

Hiện tại, Chrome sẽ thận trọng khi loại bỏ trang và chỉ làm vậy khi tin tưởng là việc đó sẽ không ảnh hưởng đến người dùng. Ví dụ: các trang phát hiện thấy bất kỳ hành động nào sau đây khi ở trạng thái ẩn sẽ không bị loại bỏ trừ phi chịu các hạn chế cực kỳ nghiêm trọng 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 thông báo
  • Đang gửi thông báo đẩy

Đối với các tính năng hiện tại trong danh sách dùng để xác định xem một thẻ có thể an toàn hay không đông lạnh hoặc bị loại bỏ, xem bài viết: Khoa học kỹ thuật đông lạnh và Đang 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ối ưu hoá điều hướng mà một số trình duyệt triển khai bằng cách sử dụng nút quay lại nút tiến nhanh hơn.

Khi người dùng điều hướng khỏi một trang, các trình duyệt này sẽ đóng băng phiên bản của trang đó để trang có thể nhanh chóng tiếp tục chạy 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 unload trình xử lý sự kiện ngăn chặn việc tối ưu hoá này.

Đối với tất cả ý định và mục đích, quá trình treo này có chức năng giống như các trình duyệt đóng băng hoạt động để tiết kiệm CPU/pin; vì lý do đó, đượ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 API không đồng bộ ở trạng thái bị cố định hoặc 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, tác vụ có thể cố định trong hàng đợi tác vụ của trang bị tạm ngưng, tức là các API không đồng bộ và API dựa trên lệnh gọi lại như IndexedDB không thể sử dụng một cách đáng tin cậy.

Trong tương lai, chúng ta sẽ thêm một phương thức commit() vào các đối tượng IDBTransaction. Phương thức này sẽ giúp nhà phát triển thực hiện những giao dịch chỉ ghi hiệu quả không yêu cầu gọi lại. Nói cách khác, nếu nhà phát triển chỉ viết dữ liệu đến IndexedDB và không thực hiện giao dịch phức tạp bao gồm các lần đọ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ụ được 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 ngay hôm nay, nhà phát triển có hai lựa chọn:

  • Sử dụng bộ nhớ phiên: Bộ nhớ phiên có tính đồng bộ và được duy trì qua các lần huỷ trang.
  • Sử dụng IndexedDB từ trình chạy dịch vụ của bạn: 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ị loại bỏ. Trong freeze hoặc Trình nghe sự kiện pagehide bạn có thể gửi dữ liệu đến trình chạy dịch vụ của mình qua postMessage()! và trình chạy dịch vụ có thể xử lý việc lưu dữ liệu.

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

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

Chrome loại bỏ giao diện người dùng
Chrome loại bỏ giao diện người dùng

Việc này giúp bạn đảm bảo trang của bạn xử lý freezeresume đúng cách các sự kiện cũng như cờ document.wasDiscarded khi các trang được tải lại sau huỷ.

Tóm tắt

Những 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 xây dựng ứng dụng với sự lưu ý đến trạng thái Vòng đời trang. Điều quan trọng là các trang web không sử dụng quá nhiều tài nguyên hệ thống trong trường hợp người dùng không mong đợi

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