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 đó là do việc chỉnh sửa 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 không đúng thời gian hoặc chạy trong thời gian dài là nguyên nhân phổ biến gây ra vấn đề về hiệu suất. Bạn nên tìm cách giảm thiểu tác động của hành vi đó nếu có thể.

JavaScript thường kích hoạt các thay đổi về hình ảnh. Đôi khi đó trực tiếp thông qua các thao tác tạo phong cách và đôi khi là các tính toán 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. Đúng giờ hoặc JavaScript chạy trong thời gian dài 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 hành vi đó nếu có thể.

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

Tuy nhiên, với tất cả những gì đã nói, có một số việc bạn chắc chắn có thể làm để giúp ứng dụng của mình thực thi JavaScript tốt.

Tóm tắt

  • Tránh setExpiration 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 Web Workers.
  • Sử dụng các tác vụ vi mô để thực hiện các thay đổi đối với DOM trên nhiều khung hình.
  • Sử dụng Dòng thời gian và Trình phân tích tài nguyên JavaScript trong Công cụ của Chrome cho nhà phát triển để đá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 diễn ra trên màn hình, bạn muốn thực hiện công việc vào đúng thời điểm cho trình duyệt, ở 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 hình sẽ 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ể 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 đề với thao tác này là lệnh gọi lại sẽ chạy tại một thời điểm trong khung hình, có thể phải ở cuối và điều này thường có tác động khiến chúng ta bỏ lỡ khung hình, dẫn đến hiện tượng giật.

set khoá của bạn 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. Mật khẩu đã được đổi để sử dụng requestAnimationFrame trong phiên bản 3. Nếu đang sử dụng phiên bản jQuery cũ hơn, bạn có thể vá ứng dụng để sử dụng requestAnimationFrame, Đây là việc nên làm.

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

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

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

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 Web Workers, chẳng hạn như nếu tính năng đó không yêu cầu quyền truy cập DOM. Thao túng hoặc truyền tải 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ư 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 tất cả công việc đều có thể phù hợp với mô hình này: Nhân viên web không có quyền truy cập 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 áp dụng phương pháp phân lô, 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ụ mất không quá vài mili giây và chạy bên trong trình xử lý requestAnimationFrame trên mỗi khung hình.

Phương pháp này có những hệ quả về trải nghiệm người dùng và giao diện người dùng và bạn cần đảm bảo người dùng biế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 không có luồng, giúp ứng dụng luôn phản hồi tương tác của người dùng.

Biết rõ "thuế khung" của JavaScript

Khi đánh giá khung, thư viện hoặc mã của riêng bạn, bạn cần đánh giá chi phí để chạy mã JavaScript theo từng khung hình. Đây là đặ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

Phần Main cung cấp biểu đồ hình ngọn lửa về 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 lượng mỗi hàm.

Nhờ có thông tin này, bạn có thể đánh giá tác động của JavaScript trên ứng dụng của bạn, đồng thời bắt đầu tìm và khắc phục mọi điểm phát sóng mà mất quá nhiều thời gian để thực thi. Như đã đề cập trước đó, bạn nên tìm kiếm để xoá JavaScript chạy trong thời gian dài hoặc nếu không thể, hãy di chuyển đoạn mã đối với Web Worker 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 sử dụng tính năng phân tích hiệu suất trong 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

Thật tuyệt 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 một việc 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 điện toán getBoundingClientRect(), nhưng hầu như luôn đúng là bạn sẽ chỉ gọi các hàm như chỉ xuất hiện vài lần trên mỗi khung hình, nên thường lãng phí nỗ lực để tập trung vào khía cạnh này của Hiệu suất của JavaScript. Thông thường, bạn sẽ chỉ tiết kiệm được các phần nhỏ 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ần nhiều chi phí tính toán thì bạn có thể là một ngoại lệ hướng dẫn này, vì bạn thường sẽ đưa nhiều phép tính vào một khung và trong trong trường hợp đó, mọi thứ đều hữu ích.

Tóm lại, bạn nên thận trọng 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.