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, việc này xảy ra trực tiếp thông qua các thao tác với kiểu, và đôi khi là các phép tính dẫn đến 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 không đúng thời điểm 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 vấn đề này khi có thể.

JavaScript thường kích hoạt các thay đổi về hình ảnh. Đôi khi, điều đó xảy ra trực tiếp thông qua các thao tác kiểu, và đôi khi là các phép tính dẫn đến 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 không đúng thời điểm 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 vấn đề này khi có thể.

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

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

Tóm tắt

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

Sử dụng requestAnimationFrame cho các thay đổi về hình ảnh

Khi các thay đổi về hình ảnh đang diễn ra trên màn hình, bạn muốn thực hiện công việc của mình vào đúng thời điểm cho trình duyệt, tức là ngay từ đầu khung. Cách duy nhất để đảm bảo rằng JavaScript 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);

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 thời điểm trong khung, có thể ngay ở cuối, và điều này 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ỡ một khung hình.

Trên thực tế, jQuery từng sử dụng setTimeout cho hành vi animate. Phương thức này đã được thay đổi để sử dụng requestAnimationFrame trong phiên bản 3. Nếu đang sử dụng phiên bản jQuery cũ, bạn nên sửa lỗi để sử dụng requestAnimationFrame.

Giảm độ phức tạp hoặc sử dụng Web Worker

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 kiểu, bố cục và trong nhiều trường hợp, cả vẽ. Nếu chạy trong thời gian dài, JavaScript sẽ chặn các tác vụ khác này, có thể khiến bạn bỏ lỡ các khung hình.

Bạn nên có chiến thuật về thời điểm chạy JavaScript và thời lượng chạy. Ví dụ: nếu đang ở trong một ảnh động như cuộn, thì tốt nhất bạn nên giữ JavaScript ở mức 3-4 mili giây. Nếu lâu hơn, bạn có nguy cơ mất quá nhiều thời gian. Nếu đang ở trạng thái rảnh, bạn có thể thoải mái hơn về thời gian cần thiết.

Trong nhiều trường hợp, bạn có thể chuyển công việc tính toán thuần tuý sang Trình chạy web, ví dụ: nếu công việc đó không yêu cầu quyền truy cập DOM. Thao tác hoặc truy cập dữ liệu, 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ư việc 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 công việc nào cũng phù hợp với mô hình này: Web Worker không có quyền truy cập vào DOM. Nếu công việc của bạn 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 đoạn tác vụ lớn thành các tác vụ vi mô, mỗi tác vụ không mất quá vài mili giây và chạy bên trong trình xử lý requestAnimationFrame trên mỗi khung.

Phương pháp này có những hậu quả về trải nghiệm người dùng và giao diện người dùng. 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 hoạt động. Trong mọi trường hợp, phương pháp này sẽ giúp luồng chính của ứng dụng luôn rảnh, giúp ứng dụng luôn phản hồi được các hoạt động tương tác của người dùng.

Tìm hiểu về "thuế khung" của JavaScript

Khi đánh giá một khung, thư viện hoặc mã của riêng bạn, điều quan trọng là bạ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ông việ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í của 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

Mục Main (Chính) cung cấp biểu đồ hình 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 những hàm nào được gọi và mỗi hàm mất bao lâu.

Nhờ có thông tin này, bạn có thể đánh giá tác động của JavaScript đối với hiệu suất của ứng dụng, đồng thời bắt đầu tìm và khắc phục mọi điểm nóng mà 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 di chuyển JavaScript đó sang một Web Worker để giải phóng luồng chính nhằm tiếp tục thực hiện các tác vụ khác.

Hãy 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

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ứ nhanh hơn 100 lần so với phiên bản 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 hầu như luôn đúng là bạn sẽ chỉ gọi các hàm như những hàm này một số ít lần trên mỗi khung, vì vậy, bạn thường lãng phí công sức khi tập trung vào khía cạnh này của hiệu suất JavaScript. Thông thường, bạn sẽ chỉ tiết kiệm được một phần mili giây.

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

Tóm lại, bạn nên hết sức thận trọng với các hoạt động tối ưu hoá vi mô vì chúng thường không liên kết với loại ứng dụng mà bạn đang xây dựng.