Tối ưu hoá việc thực thi JavaScript

JavaScript thường kích hoạt các thay đổi về hình ảnh. Đôi khi trực tiếp là thông qua các thao tác về kiểu và đôi khi là các phép tính dẫn đến những thay đổi về hình ảnh, chẳng hạn như tìm kiếm hoặc sắp xếp dữ liệu. JavaScript chạy trong thời gian dài hoặc bị hỏng thời gian là nguyên nhân phổ biến gây ra các vấn đề về hiệu suất. Bạn nên tìm cách giảm thiểu tác động của nó khi có thể.

Paul Lewis
Paul Lewis

JavaScript thường kích hoạt các thay đổi về hình ảnh. Đôi khi trực tiếp là các thao tác về kiểu và đôi khi là các phép tính dẫn đến những thay đổi về hình ảnh, chẳng hạn như tìm kiếm hoặc sắp xếp dữ liệu. JavaScript chạy sai thời gian hoặc chạy lâu là nguyên nhân phổ biến gây ra các vấn đề về hiệu suất. Bạn nên tìm cách giảm thiểu tác động của nó khi có thể.

Việc phân tích hiệu suất của JavaScript có thể là một nghệ thuật, vì JavaScript bạn viết không giống với mã được thực thi thực sự. Các trình duyệt hiện đại sử dụng trình biên dịch JIT cùng mọi cách tối ưu hoá và thủ thuật để cố gắng mang đến cho bạn khả năng thực thi nhanh nhất có thể. Điều này làm thay đổi đáng kể tính động của mã.

Tuy nhiên, như đã trình bày, có một số việc chắc chắn bạn có thể làm để giúp ứng dụng thực thi JavaScript tốt.

Tóm tắt

  • Tránh setTimeout hoặc setInterval để cập nhật hình ảnh; luôn sử dụng requestAnimationFrame để thay thế.
  • Di chuyển JavaScript chạy trong thời gian dài ra khỏi luồng chính sang các Trình chạy web.
  • Sử dụng các tác vụ vi mô để thực hiện thay đổi DOM trên nhiều khung.
  • Sử dụng Dòng thời gian của Công cụ của Chrome cho nhà phát triển và Trình phân tích tài nguyên JavaScript để đánh giá tác động của JavaScript.

Sử dụng requestAnimationFrame để thay đổi hình ảnh

Khi các thay đổi về hình ảnh đang diễn ra trên màn hình, bạn nên thực hiện công việc vào thời điểm thích hợp cho trình duyệt, tức là ngay ở đầu khung hình. Cách duy nhất để đảm bảo rằng JavaScript của bạn sẽ chạy ở đầu khung là sử dụng requestAnimationFrame.

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

Các khung hoặc mẫu có thể sử dụng setTimeout hoặc setInterval để thực hiện các thay đổi về hình ảnh như ảnh động, nhưng vấn đề ở đây là lệnh gọi lại sẽ chạy tại một điểm nào đó trong khung, có thể là ngay ở cuối khung hình và thường có thể khiến chúng ta bỏ lỡ một khung hình, dẫn đến hiện tượng giật.

setTimeout khiến trình duyệt bỏ lỡ khung.

Trên thực tế, jQuery từng sử dụng setTimeout cho hành vi animate. Thay đổi chính sách này để sử dụng requestAnimationFrame trong phiên bản 3. Nếu đang sử dụng phiên bản jQuery cũ, bạn có thể bản vá để sử dụng requestAnimationFrame. Bạn nên thực hiện thao tác này.

Giảm sự phức tạp hoặc sử dụng Trình chạy web

JavaScript chạy trên luồng chính của trình duyệt, ngay bên cạnh các phép tính toán kiểu, bố cục và trong nhiều trường hợp là tô màu. Nếu JavaScript chạy trong thời gian dài, thì JavaScript sẽ chặn các tác vụ khác này, có thể khiến khung hình bị bỏ qua.

Bạn nên có chiến thuật về thời điểm và thời lượng chạy JavaScript. Ví dụ: nếu đang ở trong ảnh động như cuộn, thì bạn nên tìm cách giữ cho JavaScript của mình ở nội dung nào đó trong khoảng 3-4 mili giây. Nếu lâu hơn thế và bạn có nguy cơ mất quá nhiều thời gian. Nếu đang trong thời gian không hoạt động, bạn có thể thoải mái hơn về thời gian sử dụng.

Trong nhiều trường hợp, bạn có thể chuyển tác vụ điện toán thuần tuý sang Trình chạy web, chẳng hạn như trong trường hợp không yêu cầu quyền truy cập DOM. Thao tác hoặc truyền tải dữ liệu, chẳng hạn như sắp xếp hoặc tìm kiếm, thường phù hợp với mô hình này, cũng như trong quá trình tải và tạo mô hình.

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

Không phải mọi công việc đều có thể phù hợp với mô hình này: Trình chạy web không có quyền truy cập DOM. Khi công việc phải nằm trên luồng chính, hãy cân nhắc phương pháp xử lý hàng loạt, trong đó bạn phân chia nhiệm vụ lớn hơn thành các tác vụ nhỏ, mỗi tác vụ mất không quá vài mili giây và chạy bên trong các trình xử lý requestAnimationFrame trên mỗi khung hình.

Phương pháp này có nhiều hệ quả về trải nghiệm người dùng và giao diện người dùng, nên bạn cần đảm bảo người dùng biết rằng một tác vụ đang được xử lý, bằng cách sử dụng chỉ báo tiến trình hoặc chỉ báo hoạt động. Trong mọi trường hợp, phương pháp này sẽ giữ cho luồng chính của ứng dụng không bị gián đoạn, giúp ứng dụng luôn phản hồi với các tương tác của người dùng.

Biết rõ “thuế khung” của JavaScript

Khi đánh giá một khung, thư viện hoặc mã của chính bạn, bạn cần phải đánh giá chi phí để chạy mã JavaScript trên cơ sở từng khung hình. Điều này đặc biệt quan trọng khi thực hiện các thao tác ảnh động quan trọng về hiệu suất như chuyển đổi hoặc cuộn.

Bảng điều khiển hiệu suất của Công cụ của Chrome cho nhà phát triển là cách tốt nhất để đo lường chi phí JavaScript. Thông thường, bạn sẽ nhận được các bản ghi cấp thấp như sau:

Bản ghi hiệu suất trong Công cụ của Chrome cho nhà phát triển

Phần Main cung cấp biểu đồ ngọn lửa của các lệnh gọi JavaScript để bạn có thể phân tích chính xác hàm nào được gọi và thời gian thực hiện mỗi hàm.

Khi cung cấp thông tin này, bạn có thể đánh giá tác động về hiệu suất của JavaScript đối với ứng dụng của mình, đồng thời bắt đầu tìm và khắc phục bất kỳ điểm phát sóng nào khiến các hàm mất quá nhiều thời gian để thực thi. Như đã đề cập trước đó, bạn nên tìm cách xoá JavaScript chạy trong thời gian dài hoặc nếu không thể, hãy chuyển mã đó sang một Trình chạy web đang giải phóng luồng chính để tiếp tục các tác vụ khác.

Xem bài viết Bắt đầu phân tích hiệu suất thời gian chạy để tìm hiểu cách sử dụng bảng điều khiển Hiệu suất.

Tránh tối ưu hoá vi mô JavaScript của bạn

Bạn có thể thấy thú vị khi biết rằng trình duyệt có thể thực thi một phiên bản của một thứ gì đó nhanh hơn 100 lần so với các trình duyệt khác, chẳng hạn như việc yêu cầu offsetTop của một phần tử nhanh hơn so với việc tính toán getBoundingClientRect(), nhưng có lẽ bạn chỉ gọi các hàm như vậy với số lần nhỏ trên mỗi khung hình. Vì vậy, việc tập trung vào khía cạnh hiệu suất của JavaScript thường sẽ là lãng phí. Thông thường, bạn sẽ chỉ lưu được vài phần của mili giây.

Nếu bạn đang tạo một trò chơi hoặc một ứng dụng có chi phí tính toán tốn kém, thì có thể bạn là trường hợp ngoại lệ của hướng dẫn này, vì bạn thường sẽ kết hợp nhiều phép tính vào một khung hình và trong trường hợp đó, mọi thứ đều hữu ích.

Tóm lại, bạn nên hết sức cảnh giác với các tối ưu hoá vi mô vì chúng thường sẽ không ánh xạ đến loại ứng dụng bạn đang xây dựng.