Cải thiện hiệu suất của ứng dụng HTML5

Giới thiệu

HTML5 cung cấp cho chúng ta các công cụ tuyệt vời để cải thiện giao diện trực quan của các ứng dụng web. Điều này đặc biệt đúng trong lĩnh vực ảnh động. Tuy nhiên, cùng với sức mạnh mới này cũng đi kèm với những thách thức mới. Thực ra những thử thách này không thực sự mới mẻ và đôi khi có lẽ bạn nên hỏi người hàng xóm thân thiện của bạn, lập trình viên Flash, về cách cô ấy đã vượt qua những vấn đề tương tự trong quá khứ.

Dù sao, khi bạn thao tác với ảnh động, điều cực kỳ quan trọng là người dùng cảm thấy các ảnh động này mượt mà. Điều chúng ta cần nhận thấy là thực sự không thể tạo ra độ mượt của ảnh động chỉ bằng cách tăng số khung hình trên giây vượt quá bất kỳ ngưỡng nhận thức nào. Thật không may, bộ não của chúng ta thông minh hơn thế. Những gì bạn sẽ học được là 30 khung hình hoạt hình mỗi giây (fps) thực sự tốt hơn nhiều so với 60 fps chỉ với một vài khung hình bị giảm giữa chừng. Mọi người ghét sự cồng kềnh.

Bài viết này sẽ cố gắng cung cấp cho bạn các công cụ và kỹ thuật để cải thiện trải nghiệm ứng dụng của riêng bạn.

Chiến lược

Chúng tôi không muốn ngăn bạn xây dựng các ứng dụng tuyệt vời, có hình ảnh tuyệt đẹp bằng HTML5.

Sau đó, khi bạn nhận thấy hiệu suất có thể tốt hơn một chút, hãy quay lại đây và đọc tiếp về cách bạn có thể cải thiện các thành phần của ứng dụng. Tất nhiên, việc thực hiện một số việc ngay từ đầu có thể sẽ giúp ích cho bạn, nhưng đừng để điều đó cản trở bạn làm việc hiệu quả.

Độ trung thực hình ảnh++ với HTML5

Tăng tốc phần cứng

Tăng tốc phần cứng là một mốc quan trọng đối với hiệu suất kết xuất tổng thể trong trình duyệt. Lược đồ chung là giảm tải các tác vụ mà CPU chính sẽ tính toán sang bộ xử lý đồ hoạ (GPU) trong bộ điều hợp đồ hoạ của máy tính. Điều này có thể mang lại hiệu suất đáng kể và cũng có thể giảm mức tiêu thụ tài nguyên trên thiết bị di động.

GPU có thể tăng tốc các khía cạnh này của tài liệu

  • Kết hợp bố cục chung
  • Chuyển đổi CSS3
  • Chuyển đổi 3D CSS3
  • Tranh vẽ trên vải canvas
  • Bản vẽ 3D WebGL

Mặc dù tăng tốc canvas và WebGL là các tính năng dùng cho mục đích đặc biệt có thể không áp dụng cho ứng dụng cụ thể của bạn, nhưng ba khía cạnh đầu tiên có thể giúp mọi ứng dụng trở nên nhanh hơn.

Có thể tăng tốc những gì?

Tính năng tăng tốc GPU hoạt động bằng cách giảm tải các tác vụ cụ thể và được xác định rõ ràng sang phần cứng có mục đích đặc biệt. Sơ đồ chung là tài liệu của bạn được chia thành nhiều "lớp" bất biến đối với các phần được tăng tốc của trang. Các lớp này được kết xuất bằng quy trình kết xuất truyền thống. Sau đó, GPU được dùng để kết hợp các lớp vào một trang duy nhất áp dụng "hiệu ứng" có thể được tăng tốc nhanh chóng. Kết quả có thể xảy ra là một đối tượng tạo ảnh động trên màn hình không yêu cầu "bố cục lại" duy nhất của trang trong khi ảnh động diễn ra.

Điều bạn cần tránh xa đó là bạn cần giúp công cụ kết xuất dễ dàng xác định thời điểm có thể áp dụng khả năng tăng tốc GPU kỳ diệu của nó. Hãy xem ví dụ sau đây:

Trong khi cách này có tác dụng, trình duyệt không thực sự biết rằng bạn đang thực hiện tác vụ được con người cảm nhận là hoạt ảnh mượt mà. Hãy cân nhắc điều gì xảy ra khi bạn có được giao diện hình ảnh giống như vậy bằng cách sử dụng hiệu ứng chuyển đổi CSS3:

Cách trình duyệt triển khai ảnh động này bị ẩn hoàn toàn đối với nhà phát triển. Điều này có nghĩa là trình duyệt có thể áp dụng các thủ thuật như tăng tốc GPU để đạt được mục tiêu đã xác định.

Có hai cờ hiệu dòng lệnh hữu ích cho Chrome để giúp gỡ lỗi tăng tốc GPU:

  1. --show-composited-layer-borders hiển thị một đường viền màu đỏ xung quanh các phần tử đang được điều khiển ở cấp GPU. Đây là một thông tin hữu ích để xác nhận các thao tác của bạn xảy ra trong lớp GPU.
  2. --show-paint-rects tất cả những thay đổi không phải GPU đều được vẽ và thao tác này sẽ tạo ra một đường viền sáng xung quanh tất cả các khu vực được sơn lại. Bạn có thể xem trình duyệt tối ưu hóa khu vực vẽ trong thực tế.

Safari có các cờ thời gian chạy tương tự như mô tả ở đây.

Chuyển đổi CSS3

Chuyển đổi CSS làm cho ảnh động tạo kiểu không quan trọng đối với mọi người, nhưng chúng cũng là một tính năng hiệu suất thông minh. Vì quá trình chuyển đổi CSS được trình duyệt quản lý nên độ trung thực của ảnh động có thể được cải thiện đáng kể và trong nhiều trường hợp phần cứng được tăng tốc. Hiện tại WebKit (Chrome, Safari, iOS) có chuyển đổi CSS tăng tốc phần cứng, nhưng tính năng này sẽ nhanh chóng có trên các trình duyệt và nền tảng khác.

Bạn có thể sử dụng các sự kiện transitionEnd để viết tập lệnh thành các kết hợp mạnh mẽ, mặc dù hiện tại, việc ghi lại tất cả các sự kiện kết thúc chuyển đổi được hỗ trợ có nghĩa là xem webkitTransitionEnd transitionend oTransitionEnd.

Nhiều thư viện hiện đã giới thiệu các API ảnh động tận dụng hiệu ứng chuyển đổi nếu có và quay lại sử dụng ảnh động kiểu DOM chuẩn. scripty2

Dịch CSS3

Tôi chắc rằng bạn đã từng tạo ảnh động cho vị trí x/y của một phần tử trên trang này. Có thể bạn đã điều khiển các thuộc tính trên cùng bên trái và trên cùng của kiểu cùng dòng. Với biến đổi 2D, chúng ta có thể sử dụng chức năng translate() để tái tạo hành vi này.

Chúng tôi có thể kết hợp yếu tố này với ảnh động DOM để sử dụng hiệu quả nhất có thể

<div style="position:relative; height:120px;" class="hwaccel">

  <div style="padding:5px; width:100px; height:100px; background:papayaWhip;
              position:absolute;" id="box">
  </div>
</div>

<script>
document.querySelector('#box').addEventListener('click', moveIt, false);

function moveIt(evt) {
  var elem = evt.target;

  if (Modernizr.csstransforms && Modernizr.csstransitions) {
    // vendor prefixes omitted here for brevity
    elem.style.transition = 'all 3s ease-out';
    elem.style.transform = 'translateX(600px)';

  } else {
    // if an older browser, fall back to jQuery animate
    jQuery(elem).animate({ 'left': '600px'}, 3000);
  }
}
</script>

Chúng tôi sử dụng Modernizr để kiểm thử tính năng cho các tính năng Chuyển đổi 2D CSS và Chuyển đổi CSS. Nếu vậy, chúng ta sẽ sử dụng tính năng dịch để chuyển vị trí. Nếu đây là ảnh động sử dụng hiệu ứng chuyển đổi, có khả năng trình duyệt phần cứng có thể tăng tốc phần cứng của nó. Để cung cấp cho trình duyệt một lần nữa theo đúng hướng, chúng tôi sẽ sử dụng "dấu đầu dòng CSS kỳ diệu" ở trên.

Nếu trình duyệt của chúng ta có khả năng kém hơn, chúng ta sẽ dự phòng jQuery để di chuyển phần tử của mình. Bạn có thể chọn trình bổ trợ jQuery Transform polyfill của Louis-Remi Babe để thực hiện toàn bộ việc này theo cách tự động.

window.requestAnimationFrame

requestAnimationFrame do Mozilla giới thiệu và sử dụng lại bởi WebKit với mục tiêu cung cấp cho bạn API gốc để chạy ảnh động, cho dù chúng dựa trên DOM/CSS hay trên <canvas> hoặc WebGL. Trình duyệt có thể tối ưu hoá các ảnh động đồng thời cùng lúc thành một chu kỳ chỉnh lại luồng và vẽ lại, giúp ảnh động có độ trung thực cao hơn. Ví dụ: ảnh động dựa trên JS được đồng bộ hoá với quá trình chuyển đổi CSS hoặc SVG SMIL. Ngoài ra, nếu bạn đang chạy vòng lặp ảnh động trong một thẻ không hiển thị, trình duyệt sẽ không tiếp tục chạy vòng lặp đó. Điều này có nghĩa là mức sử dụng CPU, GPU và bộ nhớ sẽ ít hơn, dẫn đến thời lượng pin lâu hơn nhiều.

Để biết thêm thông tin về cách thức và lý do nên sử dụng requestAnimationFrame, hãy xem bài viết requestAnimationFrame để tạo ảnh động thông minh của Paul Ireland.

Phân tích tài nguyên

Khi bạn biết tốc độ ứng dụng của mình có thể được cải thiện, đã đến lúc đào sâu vào việc phân tích để tìm ra nơi tối ưu hoá có thể mang lại lợi ích lớn nhất. Các phương pháp tối ưu hoá thường sẽ có tác động tiêu cực đến khả năng bảo trì của mã nguồn, do đó, bạn chỉ nên áp dụng các đề xuất này nếu cần. Tính năng phân tích tài nguyên cho bạn biết phần nào trong mã của bạn sẽ mang lại lợi ích lớn nhất khi hiệu suất của các phần đó được cải thiện.

Phân tích JavaScript

Trình phân tích tài nguyên JavaScript cung cấp cho bạn tổng quan về hiệu suất của ứng dụng ở cấp độ hàm JavaScript bằng cách đo lường thời gian cần thiết để thực thi từng hàm riêng lẻ từ khi bắt đầu đến khi kết thúc.

Tổng thời gian thực thi của một hàm là tổng thời gian cần thiết để thực thi hàm đó từ trên xuống dưới. Thời gian thực thi thực là tổng thời gian thực thi trừ đi thời gian cần để thực thi các hàm được gọi từ hàm.

Một số hàm được gọi thường xuyên hơn các hàm khác. Trình phân tích tài nguyên thường cho bạn biết thời gian cần thiết để chạy tất cả các lệnh gọi, cũng như thời gian thực thi trung bình, tối thiểu và tối đa.

Để biết thêm thông tin chi tiết, hãy xem tài liệu về Công cụ dành cho nhà phát triển của Chrome về việc lập hồ sơ.

DOM

Hiệu suất của JavaScript có ảnh hưởng lớn đến cảm nhận về sự linh hoạt và phản hồi của ứng dụng. Bạn cần hiểu rằng mặc dù trình phân tích tài nguyên JavaScript đo lường thời gian thực thi JavaScript của bạn, nhưng chúng cũng gián tiếp đo lường thời gian thực hiện hoạt động DOM. Các hoạt động DOM này thường là trọng tâm của các vấn đề về hiệu suất của bạn.

function drawArray(array) {
  for(var i = 0; i < array.length; i++) {
    document.getElementById('test').innerHTML += array[i]; // No good :(
  }
}

Ví dụ: trong mã ở trên hầu như không dành thời gian thực thi JavaScript thực tế. Rất có khả năng hàm drawArray sẽ xuất hiện trong hồ sơ của bạn bởi vì hàm này tương tác với DOM theo cách rất lãng phí.

Mẹo và bí quyết

Hàm ẩn danh

Hàm ẩn danh không dễ định cấu hình vì chúng vốn không có tên để có thể hiển thị trong trình phân tích tài nguyên. Có hai cách để giải quyết vấn đề này:

$('.stuff').each(function() { ... });

viết lại thành:

$('.stuff').each(function workOnStuff() { ... });

Thông thường, JavaScript có hỗ trợ đặt tên cho biểu thức hàm. Khi bạn làm như vậy, các mô-đun này sẽ xuất hiện một cách hoàn hảo trong trình phân tích tài nguyên. Có một vấn đề với giải pháp này: Biểu thức được đặt tên thực sự đặt tên hàm vào phạm vi từ vựng hiện tại. Thao tác này có thể che giấu các biểu tượng khác, vì vậy, hãy cẩn thận.

Lập hồ sơ hàm dài

Giả sử bạn có một hàm dài hạn và bạn nghi ngờ rằng một phần nhỏ hàm đó có thể là nguyên nhân dẫn đến vấn đề về hiệu suất. Có hai cách để xác định phần nào có vấn đề:

  1. Phương pháp đúng: Tái cấu trúc mã của bạn để không bao gồm các hàm dài.
  2. Phương thức hoàn tất việc thực hiện xấu: thêm các câu lệnh dưới dạng hàm tự gọi đã đặt tên vào mã của bạn. Nếu bạn cẩn thận một chút, thì việc này không thay đổi ngữ nghĩa và khiến các phần trong hàm xuất hiện dưới dạng các hàm riêng lẻ trong trình phân tích tài nguyên: js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } Đừng quên xoá các hàm bổ sung này sau khi phân tích xong; hoặc thậm chí là dùng chúng làm điểm xuất phát để tái cấu trúc mã của bạn.

Phân tích DOM

Các công cụ phát triển mới nhất trong Trình kiểm tra web Chrome có chứa "Chế độ xem theo dòng thời gian" mới cho biết tiến trình của các hành động cấp thấp mà trình duyệt thực hiện. Bạn có thể sử dụng thông tin này để tối ưu hoá các hoạt động DOM. Bạn nên cố gắng giảm số "thao tác" mà trình duyệt phải thực hiện trong khi mã của bạn thực thi.

Chế độ xem theo dòng thời gian có thể tạo ra một lượng thông tin khổng lồ. Do đó, bạn nên cố gắng tạo các trường hợp kiểm thử tối thiểu mà bạn có thể thực thi độc lập.

Phân tích DOM

Hình ảnh trên cho thấy kết quả của chế độ xem theo dòng thời gian cho một tập lệnh rất đơn giản. Ngăn bên trái hiển thị các hoạt động do trình duyệt thực hiện theo thứ tự kinh niên, còn dòng thời gian ở ngăn bên phải hiển thị thời gian thực mà một thao tác riêng lẻ đã sử dụng.

Thông tin thêm về chế độ xem theo dòng thời gian. Một công cụ thay thế để phân tích tài nguyên trong Internet Explorer là DynaTrace Ajax Edition.

Lập hồ sơ chiến lược

Tách biệt các khía cạnh

Khi bạn muốn lập hồ sơ cho ứng dụng, hãy cố gắng chọn ra những khía cạnh chức năng có thể khiến ứng dụng chạy chậm nhất có thể. Sau đó, hãy thử chạy cấu hình chỉ thực thi các phần mã có liên quan đến các khía cạnh này của ứng dụng. Điều này sẽ giúp việc diễn giải dữ liệu lập hồ sơ dễ dàng hơn vì dữ liệu này không bị trộn lẫn với các đường dẫn mã không liên quan đến vấn đề thực tế của bạn. Sau đây là một số ví dụ phù hợp về từng khía cạnh của đơn đăng ký:

  1. Thời gian khởi động (kích hoạt trình phân tích tài nguyên, tải lại ứng dụng, đợi đến khi khởi chạy xong rồi dừng trình phân tích tài nguyên.
  2. Nhấp vào một nút rồi đến ảnh động tiếp theo (bắt đầu trình phân tích tài nguyên, nhấp vào nút, đợi cho đến khi ảnh động hoàn tất, dừng trình phân tích tài nguyên).
Lập hồ sơ GUI

Trong chương trình GUI, việc chỉ thực thi đúng phần của ứng dụng có thể khó hơn so với khi bạn tối ưu hoá, chẳng hạn như bộ dò tia trong công cụ 3D. Ví dụ: khi bạn muốn lập hồ sơ nội dung xảy ra khi bạn nhấp vào một nút, bạn có thể kích hoạt các sự kiện di chuột qua không liên quan trong quá trình làm cho kết quả của bạn ít có ý nghĩa hơn. Hãy cố gắng tránh điều đó :)

Giao diện có lập trình

Ngoài ra, còn có một giao diện có lập trình để kích hoạt trình gỡ lỗi. Nhờ đó, bạn có thể kiểm soát chính xác thời điểm bắt đầu phân tích tài nguyên và kết thúc.

Bắt đầu lập hồ sơ bằng:

console.profile()

Dừng lập hồ sơ bằng:

console.profileEnd()

Khả năng lặp lại

Khi bạn lập hồ sơ, hãy đảm bảo rằng bạn thực sự có thể tái tạo kết quả của mình. Chỉ khi đó bạn mới có thể biết liệu các phương pháp tối ưu hoá của mình có thực sự cải thiện được mọi thứ hay không. Ngoài ra, việc lập hồ sơ cấp hàm được thực hiện trong bối cảnh toàn bộ máy tính của bạn. Đó không phải là một khoa học chính xác. Nhiều hoạt động khác diễn ra trên máy tính của bạn có thể ảnh hưởng đến từng hoạt động của hồ sơ:

  1. Một bộ tính giờ không liên quan trong ứng dụng của bạn sẽ kích hoạt khi bạn đo lường một mục khác.
  2. Trình thu gom rác thực hiện công việc của nó.
  3. Một thẻ khác trong trình duyệt đang hoạt động mạnh trong cùng một luồng hoạt động.
  4. Một chương trình khác trên máy tính đang sử dụng hết CPU, khiến ứng dụng chạy chậm hơn.
  5. Sự thay đổi đột ngột trong trường hấp dẫn của trái đất.

Bạn cũng nên thực thi cùng một đường dẫn mã nhiều lần trong một phiên phân tích tài nguyên. Bằng cách này, bạn giảm ảnh hưởng của các yếu tố trên và các phần bị chậm có thể nổi bật hơn nữa.

Đo lường, cải thiện, đo lường

Khi bạn xác định được điểm chậm trong chương trình của mình, hãy cố gắng tìm cách cải thiện hành vi thực thi. Sau khi bạn thay đổi mã, hãy lập lại hồ sơ. Nếu bạn hài lòng với kết quả, hãy tiếp tục. Nếu không thấy cải thiện thì có lẽ bạn nên huỷ bỏ thay đổi của mình và không giữ nguyên thay đổi "vì thay đổi đó không chịu ảnh hưởng".

Chiến lược Tối ưu hoá

Giảm thiểu tương tác DOM

Chủ đề phổ biến để cải thiện tốc độ của các ứng dụng web là giảm thiểu tương tác DOM. Mặc dù tốc độ của công cụ JavaScript đã tăng theo cấp độ độ lớn, việc truy cập DOM không nhanh hơn với cùng tốc độ. Điều này cũng là vì những lý do rất thực tế sẽ không bao giờ xảy ra (những việc như sắp xếp bố cục và vẽ nội dung trên màn hình chỉ tốn thời gian).

Lưu nút DOM vào bộ nhớ đệm

Mỗi khi bạn truy xuất một nút hoặc danh sách các nút từ DOM, hãy cố gắng suy nghĩ xem liệu bạn có thể sử dụng lại chúng trong một lần tính toán sau này hay không (hay thậm chí là chỉ trong lần lặp lại vòng lặp tiếp theo). Miễn là bạn không thực sự thêm hoặc xoá nút trong khu vực có liên quan, thì điều này thường xảy ra.

Trước:

function getElements() {
  return $('.my-class');
}

Sau:

var cachedElements;
function getElements() {
  if (cachedElements) {
    return cachedElements;
  }
  cachedElements = $('.my-class');
  return cachedElements;
}

Giá trị thuộc tính lưu vào bộ nhớ đệm

Tương tự như cách bạn có thể lưu các nút DOM vào bộ nhớ đệm, bạn cũng có thể lưu các giá trị của các thuộc tính vào bộ nhớ đệm. Hãy tưởng tượng bạn đang tạo ảnh động cho một thuộc tính kiểu của một nút. Nếu biết rằng bạn (như trong phần mã đó) là người duy nhất sẽ chạm vào thuộc tính đó, thì bạn có thể lưu giá trị cuối cùng vào bộ nhớ đệm trên mỗi lần lặp lại để không phải đọc nhiều lần.

Trước:

setInterval(function() {
  var ele = $('#element');
  var left = parseInt(ele.css('left'), 10);
  ele.css('left', (left + 5) + 'px');
}, 1000 / 30);

Sau: js var ele = $('#element'); var left = parseInt(ele.css('left'), 10); setInterval(function() { left += 5; ele.css('left', left + 'px'); }, 1000 / 30);

Di chuyển thao tác DOM ra khỏi vòng lặp

Vòng lặp thường là điểm nóng để tối ưu hoá. Hãy thử nghĩ cách để tách riêng việc xử lý số liệu thực tế để xử lý DOM. Thông thường, bạn có thể tính toán và sau đó, áp dụng tất cả kết quả trong một lần tính toán.

Trước:

document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  document.getElementById('target').innerHTML += val;
}

Sau:

var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');

Vẽ lại và chỉnh lại luồng

Như đã thảo luận trước đó, việc truy cập vào DOM tương đối chậm. Quá trình này trở nên rất chậm khi mã của bạn đang đọc một giá trị phải được tính toán lại vì gần đây mã của bạn đã sửa đổi một nội dung nào đó có liên quan trong DOM. Do đó, bạn nên tránh sử dụng quyền đọc và ghi kết hợp vào DOM. Tốt nhất là bạn nên nhóm mã theo 2 giai đoạn:

  • Giai đoạn 1: Đọc giá trị DOM cần thiết cho mã của bạn
  • Giai đoạn 2: Sửa đổi DOM

Cố gắng không lập trình một mẫu như:

  • Giai đoạn 1: Đọc các giá trị DOM
  • Giai đoạn 2: Sửa đổi DOM
  • Giai đoạn 3: Đọc thêm
  • Giai đoạn 4: Sửa đổi DOM ở nơi khác.

Trước:

function paintSlow() {
  var left1 = $('#thing1').css('left');
  $('#otherThing1').css('left', left);
  var left2 = $('#thing2').css('left');
  $('#otherThing2').css('left', left);
}

Sau:

function paintFast() {
  var left1 = $('#thing1').css('left');
  var left2 = $('#thing2').css('left');
  $('#otherThing1').css('left', left);
  $('#otherThing2').css('left', left);
}

Bạn nên cân nhắc lời khuyên này đối với các hành động diễn ra trong một ngữ cảnh thực thi JavaScript. (ví dụ: trong trình xử lý sự kiện, trong trình xử lý khoảng thời gian hoặc khi xử lý phản hồi ajax.)

Thực thi hàm paintSlow() từ phía trên sẽ tạo ra hình ảnh sau:

paintSlow()

Khi chuyển sang phương thức triển khai nhanh hơn, bạn sẽ thấy hình ảnh sau:

Triển khai nhanh hơn

Những hình ảnh này cho thấy việc sắp xếp lại cách mã truy cập vào DOM có thể tăng đáng kể hiệu suất kết xuất. Trong trường hợp này, mã gốc phải tính toán lại kiểu và bố cục trang hai lần để tạo ra cùng một kết quả. Phương pháp tối ưu hoá tương tự có thể được áp dụng về cơ bản cho mọi mã trong "thế giới thực" và mang lại một số kết quả thực sự ấn tượng.

Đọc thêm: Kết xuất: vẽ lại, chỉnh lại luồng/bố cục lại, đổi kiểu của Stoyan Stefanov

Vẽ lại và Vòng lặp sự kiện

Quá trình thực thi JavaScript trong trình duyệt tuân theo mô hình "Vòng lặp sự kiện". Theo mặc định, trình duyệt ở trạng thái "không hoạt động". Trạng thái này có thể bị gián đoạn do các sự kiện từ hoạt động tương tác của người dùng hoặc các hoạt động như bộ tính giờ JavaScript hoặc lệnh gọi lại Ajax. Bất cứ khi nào một đoạn JavaScript chạy tại điểm gián đoạn như vậy, trình duyệt thường sẽ đợi cho đến khi chạy xong cho đến khi vẽ lại màn hình (có thể có ngoại lệ đối với JavaScript chạy rất lâu hoặc trong các trường hợp như hộp cảnh báo làm gián đoạn hiệu quả việc thực thi JavaScript).

Hậu quả

  1. Nếu chu kỳ hoạt ảnh JavaScript của bạn mất hơn 1/30 giây để thực thi, bạn sẽ không thể tạo hoạt ảnh mượt vì trình duyệt sẽ không vẽ lại trong quá trình thực thi JS. Nếu muốn xử lý cả các sự kiện của người dùng, bạn cần phải nhanh hơn nhiều.
  2. Đôi khi, bạn nên trì hoãn một số thao tác JavaScript cho đến chỉ một chút sau đó. Ví dụ: setTimeout(function() { ... }, 0) Điều này rất hiệu quả để yêu cầu trình duyệt thực thi lệnh gọi lại ngay khi vòng lặp sự kiện không hoạt động trở lại (thực tế là một số trình duyệt sẽ chờ ít nhất 10 mili giây). Bạn cần lưu ý rằng thao tác này sẽ tạo ra hai chu kỳ thực thi JavaScript rất gần nhau về thời gian. Cả hai đều có thể kích hoạt việc sơn lại màn hình, giúp tăng gấp đôi tổng thời gian dành cho việc vẽ. Việc trình duyệt này có thực sự kích hoạt hai lần vẽ hay không phụ thuộc vào phương pháp phỏng đoán trong trình duyệt.

Phiên bản thông thường:

function paintFast() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  $('#otherThing2').css('height', '20px');
}
Vẽ lại và Vòng lặp sự kiện

Hãy thêm một số thời gian trễ:

function paintALittleLater() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  setTimeout(function() {
    $('#otherThing2').css('height', '20px');
  }, 10)
}
Thời gian trễ

Phiên bản bị trễ cho thấy trình duyệt vẽ hai lần mặc dù hai thay đổi đối với trang chỉ là 1/100 của một phần giây.

Khởi động Lazy

Người dùng muốn những ứng dụng web tải nhanh và có cảm giác phản hồi nhanh. Tuy nhiên, người dùng có các ngưỡng khác nhau về mức độ mà họ cho là chậm tuỳ thuộc vào hành động mà họ thực hiện. Ví dụ: ứng dụng không được thực hiện nhiều phép tính đối với sự kiện di chuột qua vì điều này có thể tạo ra trải nghiệm người dùng kém trong khi người dùng tiếp tục di chuyển chuột. Tuy nhiên, người dùng quen với việc chấp nhận một chút chậm trễ sau khi nhấp vào nút.

Do đó, bạn nên di chuyển mã khởi tạo để thực thi càng muộn càng tốt (ví dụ: khi người dùng nhấp vào nút kích hoạt một thành phần cụ thể trong ứng dụng).

Trước: js var things = $('.ele > .other * div.className'); $('#button').click(function() { things.show() });

Sau: js $('#button').click(function() { $('.ele > .other * div.className').show() });

Uỷ quyền sự kiện

Việc trải các trình xử lý sự kiện trên một trang có thể mất tương đối dài và cũng có thể tẻ nhạt khi các phần tử được thay thế động. Việc này sẽ yêu cầu phải đính kèm lại trình xử lý sự kiện vào các phần tử mới.

Giải pháp trong trường hợp này là sử dụng một kỹ thuật có tên là uỷ quyền sự kiện. Thay vì đính kèm từng trình xử lý sự kiện riêng lẻ vào các phần tử, tính chất bong bóng của nhiều sự kiện trình duyệt được sử dụng bằng cách thực sự đính kèm trình xử lý sự kiện vào nút mẹ và kiểm tra nút đích của sự kiện để xem sự kiện đó có được quan tâm hay không.

Trong jQuery, điều này có thể dễ dàng được biểu thị như sau:

$('#parentNode').delegate('.button', 'click', function() { ... });

Những trường hợp không nên sử dụng tính năng uỷ quyền sự kiện

Đôi khi điều ngược lại có thể đúng: Bạn đang sử dụng tính năng uỷ quyền sự kiện và đang gặp vấn đề về hiệu suất. Về cơ bản, việc uỷ quyền sự kiện cho phép thời gian khởi chạy với độ phức tạp không đổi. Tuy nhiên, mức giá kiểm tra xem một sự kiện có được quan tâm hay không phải được thanh toán cho mỗi lệnh gọi sự kiện đó. Điều này có thể tốn kém, đặc biệt là đối với các sự kiện xảy ra thường xuyên như "di chuột" hoặc thậm chí là "di chuyển".

Các vấn đề điển hình và giải pháp

Những việc tôi làm ở $(document).ready mất nhiều thời gian

Lời khuyên cá nhân của Malte: Đừng làm gì trong $(document).ready. Cố gắng cung cấp tài liệu ở dạng hoàn chỉnh. Được rồi, bạn được phép đăng ký trình nghe sự kiện, nhưng chỉ sử dụng bộ chọn mã và/hoặc dùng tính năng uỷ quyền sự kiện. Đối với các sự kiện tốn kém như "Mousemove", hãy trì hoãn việc đăng ký cho đến khi cần đến (sự kiện di chuột trên phần tử có liên quan).

Và nếu bạn thực sự cần làm gì đó, chẳng hạn như tạo yêu cầu Ajax để lấy dữ liệu thực tế, thì hiển thị ảnh động đẹp mắt; bạn có thể muốn đưa ảnh động vào dưới dạng URI dữ liệu nếu đó là ảnh GIF động hoặc nội dung tương tự.

Vì tôi đã thêm phim Flash vào trang, mọi thứ thực sự chậm

Việc thêm Flash vào một trang sẽ luôn làm chậm quá trình kết xuất một chút vì bố cục cuối cùng của cửa sổ phải được "thương lượng" giữa trình duyệt và plugin Flash. Khi bạn không thể tránh hoàn toàn việc đặt Flash trên các trang của mình, hãy đảm bảo bạn đặt tham số Flash "wmode" thành giá trị "window" (giá trị mặc định). Thao tác này sẽ vô hiệu hóa khả năng kết hợp các phần tử HTML và Flash (Bạn sẽ không thể thấy phần tử HTML nằm phía trên phim Flash và phim Flash của bạn không thể trong suốt). Điều này có thể gây bất tiện nhưng sẽ giúp cải thiện đáng kể hiệu suất của bạn. Ví dụ: hãy xem cách youtube.com cẩn thận tránh đặt các lớp phía trên trình phát phim chính.

Tôi đang lưu mọi thứ vào localStorage, giờ ứng dụng của tôi bị gián đoạn

Việc ghi vào localStorage là một hoạt động đồng bộ liên quan đến việc quay ổ đĩa cứng của bạn. Bạn không bao giờ muốn thực hiện các thao tác đồng bộ "chạy trong thời gian dài" trong khi tạo ảnh động. Di chuyển quyền truy cập vào localStorage đến một vị trí trong mã mà bạn chắc chắn rằng người dùng đang rảnh và không có ảnh động nào đang diễn ra.

Phân tích điểm đến bộ chọn jQuery thực sự chậm

Trước tiên, bạn cần đảm bảo rằng bộ chọn của mình có thể chạy thông qua document.querySelectorAll. Bạn có thể kiểm thử điều đó trong bảng điều khiển JavaScript. Nếu có trường hợp ngoại lệ, hãy ghi lại bộ chọn để không sử dụng bất kỳ phần mở rộng đặc biệt nào của khung JavaScript. Thao tác này sẽ tăng tốc bộ chọn trong các trình duyệt hiện đại theo thứ tự cường độ.

Nếu cách này không hiệu quả hoặc nếu bạn cũng muốn tăng tốc trong các trình duyệt hiện đại, hãy làm theo các nguyên tắc sau:

  • Hãy nêu càng cụ thể ở phía bên phải bộ chọn càng tốt.
  • Sử dụng tên thẻ mà bạn không thường xuyên sử dụng làm phần bộ chọn ngoài cùng bên phải.
  • Nếu không có cách nào hữu ích, hãy suy nghĩ về việc viết lại mọi thứ để bạn có thể sử dụng bộ chọn id

Tất cả các thao tác DOM này mất nhiều thời gian

Nhiều thao tác chèn, xoá và cập nhật nút DOM có thể rất chậm. Thường thì bạn có thể tối ưu hoá việc này bằng cách tạo một chuỗi html lớn và sử dụng domNode.innerHTML = newHTML để thay thế nội dung cũ. Lưu ý rằng điều này có thể thực sự không tốt cho khả năng bảo trì và có thể tạo ra các liên kết bộ nhớ trong IE, vì vậy hãy cẩn thận.

Một vấn đề phổ biến khác là mã khởi tạo của bạn có thể tạo nhiều HTML. Ví dụ: một trình bổ trợ jQuery chuyển đổi một hộp chọn thành một loạt các div vì đó là thiết kế mà mọi người muốn thiết kế mà không biết các phương pháp hay nhất về trải nghiệm người dùng. Nếu bạn thực sự muốn trang của mình tải nhanh, đừng bao giờ làm việc đó. Thay vào đó, hãy phân phối tất cả mã đánh dấu từ phía máy chủ trong biểu mẫu cuối cùng. Điều này lại có nhiều vấn đề, vì vậy hãy suy nghĩ kỹ xem tốc độ có đáng để đánh đổi hay không.

Công cụ

  1. JSPerf – Các đoạn mã JavaScript nhỏ điểm chuẩn
  2. ViewGroup – Để lập hồ sơ trong Firefox
  3. Công cụ dành cho nhà phát triển Google Chrome (Có sẵn dưới dạng WebInspector trong Safari)
  4. DOM Monster – Để tối ưu hoá hiệu suất của DOM
  5. DynaTrace Ajax Edition – Để tối ưu hoá việc phân tích và tô màu trong Internet Explorer

Tài liệu đọc thêm

  1. Tốc độ của Google
  2. Paul Ireland về Hiệu suất jQuery
  3. Hiệu suất JavaScript cực cao (Bản trình bày)