Khi xây dựng các trang web phụ thuộc nhiều hơn vào JavaScript, đôi khi chúng ta phải trả tiền cho những gì chúng ta gửi xuống theo những cách mà không phải lúc nào chúng ta cũng dễ dàng nhận thấy. Trong bài viết này, chúng tôi sẽ trình bày lý do vì sao một chút kỷ luật có thể giúp ích nếu bạn muốn trang web của mình tải và tương tác nhanh chóng trên thiết bị di động. Việc phân phối ít JavaScript hơn có thể giúp giảm thời gian truyền tải qua mạng, giảm thời gian giải nén mã và giảm thời gian phân tích cú pháp cũng như biên dịch JavaScript này.
Mạng
Khi hầu hết các nhà phát triển nghĩ về chi phí của JavaScript, họ nghĩ về chi phí này theo chi phí tải xuống và chi phí thực thi. Việc gửi nhiều byte JavaScript hơn qua mạng sẽ mất nhiều thời gian hơn khi kết nối của người dùng chậm hơn.
Đây có thể là vấn đề vì loại kết nối mạng hiệu quả mà người dùng có thể không thực sự là 3G, 4G hoặc Wi-Fi. Bạn có thể đang sử dụng Wi-Fi của quán cà phê nhưng lại kết nối với một điểm phát sóng di động có tốc độ 2G.
Bạn có thể giảm chi phí truyền tải mạng của JavaScript thông qua:
- Chỉ gửi mã mà người dùng cần.
- Sử dụng tính năng phân tách mã để chia JavaScript thành phần quan trọng và không quan trọng. Trình đóng gói mô-đun như webpack hỗ trợ phân tách mã.
- Tải từng phần mã không quan trọng.
- Rút gọn
- Sử dụng UglifyJS để rút gọn mã ES5.
- Sử dụng babel-minify hoặc uglify-es để rút gọn ES2015 trở lên.
- Nén
- Xoá mã không dùng đến.
- Xác định các cơ hội để xoá hoặc tải mã theo cách tải lười bằng mức độ sử dụng mã DevTools.
- Sử dụng babel-preset-env và browserlist để tránh chuyển đổi các tính năng đã có trong trình duyệt hiện đại. Các nhà phát triển nâng cao có thể thấy rằng việc phân tích kỹ các gói webpack sẽ giúp xác định cơ hội cắt bớt các phần phụ thuộc không cần thiết.
- Để loại bỏ mã, hãy xem nội dung loại bỏ cây, các trình bổ trợ tối ưu hoá nâng cao và cắt bớt thư viện của Trình biên dịch Closure như lodash-babel-plugin hoặc ContextReplacementPlugin của webpack cho các thư viện như Moment.js.
- Lưu mã vào bộ nhớ đệm để giảm thiểu số lần truy cập mạng.
- Sử dụng tính năng lưu vào bộ nhớ đệm HTTP để đảm bảo trình duyệt lưu các phản hồi vào bộ nhớ đệm một cách hiệu quả. Xác định thời gian hoạt động tối ưu cho tập lệnh (max-age) và cung cấp mã xác thực (ETag) để tránh chuyển các byte không thay đổi.
- Việc lưu vào bộ nhớ đệm của Trình chạy dịch vụ có thể giúp mạng ứng dụng của bạn linh hoạt và cho phép bạn truy cập nhanh vào các tính năng như bộ nhớ đệm mã của V8.
- Sử dụng tính năng lưu vào bộ nhớ đệm dài hạn để tránh phải tìm nạp lại các tài nguyên chưa thay đổi. Nếu bạn đang sử dụng Webpack, hãy xem phần hàm băm tên tệp.
Phân tích cú pháp/Biên dịch
Sau khi tải xuống, một trong những chi phí lớn nhất của JavaScript là thời gian để công cụ JS phân tích cú pháp/biên dịch mã này. Trong Chrome DevTools, quá trình phân tích cú pháp và biên dịch là một phần của thời gian "Viết tập lệnh" màu vàng trong bảng điều khiển Hiệu suất.
Thẻ Bottom-Up (Dưới lên) và Call Tree (Cây lệnh gọi) cho bạn biết thời gian chính xác để Phân tích cú pháp/biên dịch:

Nhưng tại sao điều này lại quan trọng?
Việc mất nhiều thời gian để phân tích cú pháp/biên dịch mã có thể làm chậm đáng kể thời gian người dùng có thể tương tác với trang web của bạn. Bạn gửi càng nhiều JavaScript thì càng mất nhiều thời gian để phân tích cú pháp và biên dịch trước khi trang web có thể tương tác.
Theo từng byte, trình duyệt sẽ tốn nhiều tài nguyên hơn để xử lý JavaScript so với hình ảnh hoặc Phông chữ web có kích thước tương đương — Tom Dale
So với JavaScript, có rất nhiều chi phí liên quan đến việc xử lý hình ảnh có kích thước tương đương (vẫn phải giải mã!) nhưng trên phần cứng di động trung bình, JS có nhiều khả năng ảnh hưởng tiêu cực đến khả năng tương tác của trang.

Khi chúng ta nói về việc phân tích cú pháp và biên dịch bị chậm; ngữ cảnh là rất quan trọng — chúng ta đang nói về điện thoại di động trung bình. Người dùng thông thường có thể dùng điện thoại có CPU và GPU chậm, không có bộ nhớ đệm L2/L3 và thậm chí có thể bị hạn chế bộ nhớ.
Không phải lúc nào chức năng mạng và chức năng thiết bị cũng khớp nhau. Người dùng có kết nối cáp quang tuyệt vời không nhất thiết phải có CPU tốt nhất để phân tích cú pháp và đánh giá JavaScript được gửi đến thiết bị của họ. Điều này cũng đúng theo hướng ngược lại… kết nối mạng rất kém nhưng CPU lại cực nhanh. – Kristofer Baxter, LinkedIn
Dưới đây, chúng ta có thể thấy chi phí phân tích cú pháp khoảng 1 MB JavaScript đã giải nén (đơn giản) trên phần cứng cấp thấp và cấp cao. Thời gian phân tích cú pháp/biên dịch mã giữa điện thoại nhanh nhất trên thị trường và điện thoại trung bình có sự khác biệt từ 2 đến 5 lần.

Còn trang web thực tế như CNN.com thì sao?
Trên iPhone 8 cao cấp, chỉ mất khoảng 4 giây để phân tích cú pháp/biên dịch JS của CNN so với khoảng 13 giây đối với một chiếc điện thoại trung bình (Moto G4). Điều này có thể ảnh hưởng đáng kể đến tốc độ người dùng có thể tương tác đầy đủ với trang web này.

Điều này nhấn mạnh tầm quan trọng của việc kiểm thử trên phần cứng trung bình (như Moto G4) thay vì chỉ kiểm thử trên điện thoại có thể nằm trong túi của bạn. Tuy nhiên, bối cảnh cũng quan trọng: hãy tối ưu hoá cho điều kiện thiết bị và mạng mà người dùng của bạn có.

Chúng ta có thực sự gửi quá nhiều JavaScript không? Có thể là vậy :)
Khi sử dụng Kho lưu trữ HTTP (khoảng 500.000 trang web hàng đầu) để phân tích trạng thái của JavaScript trên thiết bị di động, chúng ta có thể thấy rằng 50% trang web mất hơn 14 giây để có thể tương tác. Những trang web này mất tới 4 giây chỉ để phân tích cú pháp và biên dịch JS.
Hãy tính đến thời gian cần thiết để tìm nạp và xử lý JS cũng như các tài nguyên khác. Có thể bạn sẽ không ngạc nhiên khi người dùng phải đợi một lúc trước khi cảm thấy trang đã sẵn sàng để sử dụng. Chúng ta chắc chắn có thể làm tốt hơn ở đây.
Việc xoá JavaScript không quan trọng khỏi các trang có thể làm giảm thời gian truyền, quá trình phân tích cú pháp và biên dịch tốn nhiều CPU cũng như mức hao tổn bộ nhớ tiềm ẩn. Điều này cũng giúp các trang của bạn tương tác nhanh hơn.
Thời gian thực thi
Không chỉ việc phân tích cú pháp và biên dịch mới có thể gây tốn kém. Thực thi JavaScript (chạy mã sau khi phân tích cú pháp/biên dịch) là một trong những thao tác phải diễn ra trên luồng chính. Thời gian thực thi lâu cũng có thể làm chậm thời gian người dùng tương tác với trang web của bạn.
Nếu tập lệnh thực thi trong hơn 50 mili giây, thì thời gian tương tác sẽ bị trễ toàn bộ thời gian cần thiết để tải xuống, biên dịch và thực thi JS — Alex Russell
Để giải quyết vấn đề này, JavaScript sẽ được chia thành các phần nhỏ để tránh khóa luồng chính. Hãy tìm hiểu xem bạn có thể giảm lượng công việc đang thực hiện trong quá trình thực thi hay không.
Các chi phí khác
JavaScript có thể ảnh hưởng đến hiệu suất trang theo những cách khác:
- Bộ nhớ. Các trang có thể xuất hiện giật hoặc thường xuyên tạm dừng do GC (thu gom rác). Khi trình duyệt thu hồi bộ nhớ, quá trình thực thi JS sẽ bị tạm dừng, vì vậy, trình duyệt thường xuyên thu thập rác có thể tạm dừng quá trình thực thi thường xuyên hơn mức chúng ta mong muốn. Tránh rò rỉ bộ nhớ và các điểm tạm dừng gc thường xuyên để các trang không bị giật.
- Trong thời gian chạy, JavaScript chạy trong thời gian dài có thể chặn luồng chính khiến các trang không phản hồi. Việc chia công việc thành các phần nhỏ hơn (sử dụng
requestAnimationFrame()
hoặcrequestIdleCallback()
để lập lịch biểu) có thể giảm thiểu các vấn đề về khả năng phản hồi, giúp cải thiện Lượt tương tác đến nội dung hiển thị tiếp theo (INP).
Các mẫu để giảm chi phí phân phối JavaScript
Khi bạn cố gắng duy trì thời gian phân tích cú pháp/biên dịch và truyền mạng cho JavaScript chậm, có một số mẫu có thể giúp ích như phân đoạn dựa trên tuyến hoặc PRPL.
PRPL
PRPL (Đẩy, Hiển thị, Lưu vào bộ nhớ đệm trước, Tải lười) là một mẫu tối ưu hoá tính tương tác thông qua việc phân tách mã và lưu vào bộ nhớ đệm một cách mạnh mẽ:
Hãy cùng hình dung tác động của việc này.
Chúng tôi phân tích thời gian tải của các trang web phổ biến dành cho thiết bị di động và Ứng dụng web tiến bộ bằng cách sử dụng Số liệu thống kê về lệnh gọi trong thời gian chạy của V8. Như chúng ta có thể thấy, thời gian phân tích cú pháp (hiển thị bằng màu cam) là một phần đáng kể trong thời gian mà nhiều trang web này dành ra:
Wego, một trang web sử dụng PRPL, có thể duy trì thời gian phân tích cú pháp thấp cho các tuyến đường của họ, tương tác rất nhanh. Nhiều trang web khác trong số này đã áp dụng tính năng phân tách mã và ngân sách hiệu suất để cố gắng giảm chi phí JS.
Tự khởi động từng phần
Nhiều trang web tối ưu hoá khả năng hiển thị nội dung mà không chú trọng đến khả năng tương tác. Để có được lượt vẽ đầu tiên nhanh chóng khi bạn có các gói JavaScript lớn, đôi khi nhà phát triển sử dụng tính năng kết xuất phía máy chủ; sau đó "nâng cấp" tính năng này để đính kèm trình xử lý sự kiện khi JavaScript cuối cùng được tìm nạp.
Hãy cẩn thận vì việc này sẽ phát sinh chi phí. Bạn 1) thường gửi một phản hồi HTML lớn hơn có thể thúc đẩy khả năng tương tác của chúng ta, 2) có thể khiến người dùng rơi vào một hố sâu kỳ lạ, trong đó một nửa trải nghiệm không thực sự có thể tương tác cho đến khi JavaScript hoàn tất quá trình xử lý.
Bạn nên sử dụng phương pháp Bootstrap tăng dần. Gửi một trang có chức năng tối thiểu (chỉ bao gồm HTML/JS/CSS cần thiết cho tuyến hiện tại). Khi có thêm tài nguyên, ứng dụng có thể tải lười và mở khoá thêm nhiều tính năng.

Tải mã tương ứng với nội dung hiển thị là mục tiêu hàng đầu. PRPL và tính năng Tự động tải dần là các mẫu có thể giúp bạn thực hiện việc này.
Kết luận
Kích thước truyền tải rất quan trọng đối với các mạng cấp thấp. Thời gian phân tích cú pháp rất quan trọng đối với các thiết bị bị giới hạn bởi CPU. Bạn cần duy trì các chỉ số này ở mức thấp.
Các nhóm đã thành công khi áp dụng ngân sách hiệu suất nghiêm ngặt để giảm thời gian truyền và phân tích cú pháp/biên dịch JavaScript. Xem video "Can You Buy It?" (Bạn có đủ tiền mua không?) của Alex Russell: Ngân sách hiệu suất thực tế trên web" để biết hướng dẫn về ngân sách cho thiết bị di động.

Nếu bạn đang xây dựng một trang web nhắm đến thiết bị di động, hãy cố gắng phát triển trên phần cứng đại diện, giảm thời gian phân tích cú pháp/biên dịch JavaScript và sử dụng Ngân sách hiệu suất để đảm bảo nhóm của bạn có thể theo dõi chi phí JavaScript.
Tìm hiểu thêm
- Hội nghị nhà phát triển Chrome 2017 – Các phương pháp tải hiện đại tốt nhất
- Hiệu suất khởi động JavaScript
- Giải quyết khủng hoảng về hiệu suất web – Nolan Lawson
- Bạn có đủ khả năng chi trả không? Ngân sách hiệu suất thực tế – Alex Russell
- Đánh giá khung web và thư viện – Kristofer Baxter
- Kết quả của Cloudflare khi thử nghiệm Brotli để nén (lưu ý Brotli động ở chất lượng cao hơn có thể trì hoãn quá trình hiển thị trang ban đầu, vì vậy, hãy đánh giá cẩn thận. Bạn nên nén tĩnh.)
- Hợp đồng tương lai về hiệu suất – Sam Saccone