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 để nâng cao giao diện của ứng dụng web. Điều này đặc biệt đúng trong lĩnh vực ảnh động. Tuy nhiên, sức mạnh mới này cũng mang đến những thách thức mới. Thực ra, những thách thức này không thực sự mới mẻ và đôi khi bạn nên hỏi cô bạn lập trình viên Flash thân thiện ngồi cạnh mình cách cô đã vượt qua những vấn đề tương tự trong quá khứ.

Dù sao đi nữa, khi bạn làm việc trong ảnh động, điều quan trọng là người dùng phải cảm nhận được các ảnh động này mượt mà. Chúng ta cần nhận ra rằng không thể tạo được độ mượt trong ảnh động chỉ bằng cách tăng số khung hình/giây vượt quá ngưỡng nhận thức. Rất tiếc, bộ não của chúng ta thông minh hơn thế. Bạn sẽ biết rằng 30 khung hình ảnh động/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ị bỏ qua ở giữa. Mọi người ghét hình ảnh răng cưa.

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 của ứng dụng của riêng bạn.

Chiến lược

Chúng tôi không hề muốn ngăn cản bạn tạo ra những ứng dụng trực quan tuyệt vời 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 về cách cải thiện các thành phần của ứng dụng. Tất nhiên, việc này có thể giúp bạn làm một số việc ngay từ đầu, nhưng đừng để điều đó cản trở bạn làm việc hiệu quả.

Độ chân 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à chuyển các tác vụ mà CPU chính sẽ tính toán sang bộ xử lý đồ hoạ (GPU) trong bộ chuyển đổi đồ hoạ của máy tính. Điều này có thể mang lại hiệu suất tăng lên đá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

  • Tổng hợp bố cục chung
  • Hiệu ứng chuyển đổi CSS3
  • Biến đổi 3D CSS3
  • Vẽ trên Canvas
  • Vẽ 3D bằng WebGL

Mặc dù tính năng tăng tốc canvas và WebGL là các tính năng có 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 3 khía cạnh đầu tiên có thể giúp hầu hết mọi ứng dụng trở nên nhanh hơn.

Những gì có thể được tăng tốc?

Tính năng tăng tốc GPU hoạt động bằng cách chuyển các tác vụ cụ thể và được xác định rõ ràng sang phần cứng chuyên dụng. Lược đồ chung là tài liệu của bạn được chia thành nhiều "lớp" không đổi đối với các khía cạnh của trang được tăng tốc. 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 bằng cách áp dụng "hiệu ứng" có thể được tăng tốc ngay lập tức. Kết quả có thể xảy ra là một đối tượng được tạo ảnh động trên màn hình không yêu cầu "sắp xếp lại" trang trong khi ảnh động diễn ra.

Điều bạn cần rút ra từ đó 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 tính năng tăng tốc GPU. Hãy xem ví dụ sau đây:

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

Nhà phát triển hoàn toàn không biết cách trình duyệt triển khai ảnh động này. Đ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ờ dòng lệnh hữu ích cho Chrome để giúp gỡ lỗi tính năng tăng tốc GPU:

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

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

Hiệu ứng chuyển đổi CSS3

Hiệu ứng chuyển đổi CSS giúp mọi người dễ dàng tạo ảnh động theo kiểu, đồng thời cũng là một tính năng hiệu suất thông minh. Vì quá trình chuyển đổi CSS do 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, có thể tăng tốc phần cứng. Hiện tại, WebKit (Chrome, Safari, iOS) có các phép biến đổi CSS tăng tốc phần cứng, nhưng tính năng này sẽ sớm ra mắt 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 này thành các 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à theo dõi 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 hiệu ứng ảnh động kiểu DOM chuẩn nếu không. scripty2, hiệu ứng chuyển đổi YUI, jQuery animate enhanced.

Dịch CSS3

Tôi chắc chắn 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. Có thể bạn đã thao tác với các thuộc tính trái và trên cùng của kiểu nội tuyến. Với các phép 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 ta có thể kết hợp tính năng 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 ta sử dụng Modernizr để kiểm thử tính năng Biến đổi 2D CSS và Chuyển đổi CSS, nếu có, chúng ta sẽ sử dụng translate để dịch chuyển vị trí. Nếu ảnh động này được tạo bằng hiệu ứng chuyển đổi, thì có khả năng trình duyệt có thể tăng tốc phần cứng cho ảnh động đó. Để đẩy trình duyệt đi đúng hướng, chúng ta sẽ sử dụng "đầu đạn CSS ma thuật" ở trên.

Nếu trình duyệt của chúng ta có chức năng kém hơn, chúng ta sẽ quay lại sử dụng jQuery để di chuyển phần tử. Bạn có thể sử dụng trình bổ trợ polyfill jQuery Transform của Louis-Remi Babe để tự động hoá toàn bộ quá trình này.

window.requestAnimationFrame

requestAnimationFrame do Mozilla giới thiệu và WebKit lặp lại với mục tiêu cung cấp cho bạn một API gốc để chạy ảnh động, cho dù đó là ảnh độ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 thành một chu kỳ luân chuyển và vẽ lại duy nhất, dẫn đến ảnh động có độ trung thực cao hơn. Ví dụ: ảnh động dựa trên JS được đồng bộ hoá với hiệu ứng 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 đó, tức là mức sử dụng CPU, GPU và bộ nhớ sẽ giảm, dẫn đến thời lượng pin dài hơn nhiều.

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

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

Khi bạn phát hiện ra rằng tốc độ của ứng dụng có thể được cải thiện, đã đến lúc bạn cần tìm hiểu kỹ hơn về việc lập hồ sơ để tìm ra những điểm tối ưu hoá có thể mang lại lợi ích lớn nhất. Các hoạt động tối ưu hoá thường sẽ ảnh hưởng tiêu cực đến khả năng bảo trì mã nguồn của bạn. Do đó, bạn chỉ nên áp dụng các hoạt động này nếu cần. Tính năng phân tích tài nguyên cho bạn biết những phần mã nào 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 tài nguyên JavaScript

Trình phân tích tài nguyên JavaScript cung cấp cho bạn thông tin 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 thực thi từng hàm riêng lẻ từ lúc bắt đầu đến lúc kết thúc.

Thời gian thực thi tổng thể 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 ròng là thời gian thực thi tổng trừ đi thời gian 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 để tất cả các lệnh gọi chạy, 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ề việc lập hồ sơ trong Công cụ của Chrome cho nhà phát triển.

DOM

Hiệu suất của JavaScript có ảnh hưởng lớn đến độ linh hoạt và khả năng phản hồi của ứng dụng. Điều quan trọng là bạn cần phải 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 của JavaScript, nhưng chúng cũng gián tiếp đo lường thời gian thực hiện các thao tác DOM. Những thao tác DOM này thường là nguyên nhân chính gây ra vấn đề về hiệu suất.

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 mất thời gian để thực thi JavaScript thực tế. Rất có thể hàm drawArray sẽ xuất hiện trong hồ sơ của bạn vì hàm này đang 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

Rất khó để lập hồ sơ cho các hàm ẩn danh vì bản chất chúng không có tên để có thể xuất hiện 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() { ... });

Không nhiều người biết rằng JavaScript hỗ trợ việc đặt tên biểu thức hàm. Việc này sẽ giúp các lớp xuất hiện hoàn hảo trong trình phân tích tài nguyên. Giải pháp này có một vấn đề: 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. Việc này có thể làm hỏng các ký hiệu khác, vì vậy hãy cẩn thận.

Phân tích tài nguyên cho các hàm dài

Hãy tưởng tượng bạn có một hàm dài và nghi ngờ một phần nhỏ của hàm đó có thể là nguyên nhân gây ra vấn đề về hiệu suất. Có hai cách để tìm ra phần nào có vấn đề:

  1. Phương pháp chính xác: Tái cấu trúc mã để không bao gồm bất kỳ hàm dài nào.
  2. Phương thức làm việc hiệu quả: thêm các câu lệnh ở dạng hàm tự gọi được đặt tên vào mã của bạn. Nếu bạn cẩn thận một chút, việc này sẽ không làm thay đổi ngữ nghĩa và giúp các phần của 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 tài nguyên xong; hoặc thậm chí sử dụng các hàm này làm điểm xuất phát để tái cấu trúc mã.

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

Các công cụ phát triển mới nhất của Trình kiểm tra web của Chrome có "Chế độ xem dòng thời gian" mới, cho thấy dòng thời gian của các thao tác cấp thấp do 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 thao tác DOM. Bạn nên giảm số lượng "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 lớn thông tin. 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 tài nguyên DOM

Hình ảnh ở trên cho thấy kết quả của chế độ xem tiến trình cho một tập lệnh rất đơn giản. Ngăn bên trái cho thấy các thao tác do trình duyệt thực hiện theo thứ tự thời gian, trong khi tiến trình trong ngăn bên phải cho thấy thời gian thực tế mà một thao tác riêng lẻ tiêu thụ.

Thông tin khác về chế độ xem dòng thời gian. Một công cụ thay thế để lập hồ sơ trong Internet Explorer là DynaTrace Ajax Edition.

Chiến lược phân tích

Lấy riêng các khía cạnh

Khi bạn muốn phân tích tài nguyên cho ứng dụng, hãy cố gắng xác định những khía cạnh chức năng có thể gây ra tình trạng chậm chạp càng gần càng tốt. Sau đó, hãy thử chạy một hồ sơ chỉ thực thi các phần mã liên quan đến những khía cạnh này của ứng dụng. Điều này sẽ giúp bạn dễ dàng diễn giải dữ liệu phân tích tài nguyên hơn vì dữ liệu này không bị lẫ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ụ về các khía cạnh riêng lẻ của ứng dụng:

  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 cho đến khi quá trình khởi chạy hoàn tất, dừng trình phân tích tài nguyên.
  2. Nhấp vào một nút và ả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).
Phân tích tài nguyên giao diện người dùng đồ hoạ

Việc chỉ thực thi phần phù hợp của ứng dụng có thể khó hơn trong chương trình GUI so với khi bạn tối ưu hoá, chẳng hạn như trình theo dõi tia của công cụ 3D. Ví dụ: khi muốn lập hồ sơ về những gì 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 không liên quan trong quá trình này, khiến kết quả của bạn kém thuyết phục. Hãy cố gắng tránh điều đó :)

Giao diện lập trình

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

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 lập hồ sơ, hãy đảm bảo bạn có thể tái tạo kết quả. Chỉ khi đó, bạn mới có thể biết liệu các biện pháp tối ưu hoá của mình có thực sự cải thiện hiệu suất hay không. Ngoài ra, việc phân tích tài nguyên ở cấp hàm được thực hiện trong bối cảnh toàn bộ máy tính. Đây không phải là một môn khoa học chính xác. Quá trình chạy từng hồ sơ riêng lẻ có thể chịu ảnh hưởng của nhiều yếu tố khác đang diễn ra trên máy tính của bạn:

  1. Một bộ hẹn giờ không liên quan trong ứng dụng của riêng bạn sẽ kích hoạt trong khi bạn đo lường một giá trị khác.
  2. Trình thu gom rác đang hoạt động.
  3. Một thẻ khác trong trình duyệt của bạn đang làm việc chăm chỉ 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 của bạn đang sử dụng hết CPU, khiến ứng dụng của bạn chạy chậm hơn.
  5. Sự thay đổi đột ngột trong trường trọng lực 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 được ả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 một điểm chậm trong chương trình, hãy cố gắng nghĩ ra cách cải thiện hành vi thực thi. Sau khi bạn thay đổi mã, hãy lập hồ sơ lại. 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 sự cải thiện, bạn nên quay lại thay đổi và không để nguyên trạng "vì không có gì hại cả".

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

Giảm thiểu hoạt động tương tác DOM

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

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

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

Trước:

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

Sau:

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

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

Tương tự như cách 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 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 của kiểu nút. Nếu biết rằng bạn (trong phần mã đó) là người duy nhất sẽ chạm vào thuộc tính đó, bạn có thể lưu giá trị cuối cùng vào bộ nhớ đệm trong mỗi lần lặp lại để không phải đọc thuộc tính đó 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 cố gắng nghĩ ra cách tách biệt việc tính toán số liệu thực tế với việc xử lý DOM. Thông thường, bạn có thể thực hiện một phép tính và sau khi hoàn tất, áp dụng tất cả kết quả cùng một lúc.

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ảy lại

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 sẽ diễn ra 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ì mã của bạn gần đây đã sửa đổi một nội dung liên quan trong DOM. Do đó, bạn nên tránh kết hợp quyền đọc và ghi vào DOM. Tốt nhất là bạn nên luôn nhóm mã của mình theo hai giai đoạn:

  • Giai đoạn 1: Đọc cá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ẫ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 cho 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.)

Việc thực thi hàm paintSlow() ở trên sẽ tạo ra hình ảnh sau:

paintSlow()

Việc chuyển sang phương thức triển khai nhanh hơn sẽ tạo ra 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ể cải thiện đá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 các kiểu và bố cục trang hai lần để tạo ra cùng một kết quả. Về cơ bản, bạn có thể áp dụng phương pháp tối ưu hoá tương tự cho tất cả mã "thực tế" và thu được một số kết quả thực sự ấn tượng.

Đọc thêm: Kết xuất: vẽ lại, luồng lại/bố cục lại, định kiểu lại 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 "rảnh". Trạng thái này có thể bị gián đoạn bởi các sự kiện từ hoạt động tương tác của người dùng hoặc các sự kiện như trình hẹn giờ JavaScript hoặc lệnh gọi lại Ajax. Bất cứ khi nào một đoạn mã JavaScript chạy tại một điểm gián đoạn như vậy, trình duyệt thường sẽ đợi đoạn mã đó chạy xong cho đến khi vẽ lại màn hình (Có thể có ngoại lệ đối với các đoạn mã JavaScript chạy trong thời gian cực dài 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ả quá trình thực thi JavaScript).

Hậu quả

  1. Nếu chu kỳ ảnh động JavaScript của bạn mất nhiều hơn 1/30 giây để thực thi, bạn sẽ không thể tạo ảnh động mượt mà vì trình duyệt sẽ không vẽ lại trong quá trình thực thi JS. Khi bạn cũng muốn xử lý 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 một lúc sau. Ví dụ: setTimeout(function() { ... }, 0) Thao tác này sẽ 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 trở lại trạng thái rảnh (một số trình duyệt sẽ đợi ít nhất 10 mili giây). Bạn cần lưu ý rằng việ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 vẽ lại màn hình, điều này có thể làm tăng gấp đôi thời gian tổng thể cho việc vẽ. Việc 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 chút độ trễ:

function paintALittleLater() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  setTimeout(function() {
    $('#otherThing2').css('height', '20px');
  }, 10)
}
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ỉ mất 1/100 giây.

Khởi tạo từng phần

Người dùng muốn ứng dụng web tải nhanh và phản hồi nhanh. Tuy nhiên, người dùng có các ngưỡng khác nhau về những gì họ cảm nhận là chậm, tuỳ thuộc vào hành động mà họ thực hiện. Ví dụ: ứng dụng không bao giờ được thực hiện nhiều phép tính trên 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 không tốt 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 độ trễ sau khi nhấp vào một nút.

Do đó, bạn nên di chuyển mã khởi chạy để thực thi muộn nhất có thể (ví dụ: khi người dùng nhấp vào một nút kích hoạt một thành phần cụ thể của ứ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 phân phối trình xử lý sự kiện trên một trang có thể mất tương đối nhiều thời gian và cũng có thể gây phiền toái sau khi các phần tử được thay thế một cách linh động, sau đó yêu cầu đí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 vào các phần tử, bản chất 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 mục tiêu của sự kiện để xem sự kiện có quan tâm hay không.

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

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

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à gặp vấn đề về hiệu suất. Về cơ bản, tính năng uỷ quyền sự kiện cho phép thời gian khởi chạy có độ phức tạp không đổi. Tuy nhiên, bạn phải trả phí kiểm tra xem một sự kiện có quan tâm hay không cho mỗi lệnh gọi sự kiện đó. Điều này có thể gây tốn kém, đặc biệt là đối với các sự kiện xảy ra thường xuyên như "mouseover" (di chuột qua) hoặc thậm chí là "mousemove" (di chuột).

Các vấn đề thường gặp và giải pháp

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

Lời khuyên cá nhân của Malte: Không bao giờ làm gì trong $(document).ready. Hãy cố gắng gửi tài liệu ở dạng hoàn chỉnh. OK, bạn được phép đăng ký trình nghe sự kiện, nhưng chỉ được sử dụng bộ chọn mã nhận dạng và/hoặc sử 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 (sự kiện di chuột qua phần tử liên quan).

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

Kể từ khi tôi thêm một phim Flash vào trang, mọi thứ đều rất 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à trình bổ trợ Flash. Khi bạn không thể hoàn toàn tránh việc đặt Flash trên các trang của mình, hãy nhớ đặt tham số Flash "wmode" thành giá trị "window" (là giá trị mặc định). Thao tác này sẽ vô hiệu hoá 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). Việc 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 các mục vào localStorage, giờ đây ứng dụng của tôi bị giật

Việc ghi vào localStorage là một thao tác đồng bộ liên quan đến việc khởi động ổ đĩa cứng. 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 ở trạng thái rảnh và không có ảnh động nào đang diễn ra.

Phân tích tài nguyên cho thấy bộ chọn jQuery hoạt động rất 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ó ngoại lệ, hãy viết lại bộ chọn để không sử dụng bất kỳ tiện ích đặc biệt nào của khung JavaScript. Điều này sẽ tăng tốc bộ chọn của bạn trong các trình duyệt hiện đại theo thứ tự độ lớn.

Nếu cách này không hiệu quả hoặc nếu bạn cũng muốn truy cập nhanh 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ụ thể nhất có thể ở bên phải bộ chọn.
  • Sử dụng tên thẻ mà bạn không thường 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 khác, hãy cân nhắc việc viết lại để có thể sử dụng bộ chọn mã nhận dạng

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

Một loạt các thao tác chèn, xoá và cập nhật nút DOM có thể diễn ra rất chậm. Thông thường, 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ũ. Xin lưu ý rằng việc này có thể gây ảnh hưởng rất lớn đến khả năng bảo trì và có thể tạo ra các đường 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 chạy có thể tạo ra nhiều HTML. Ví dụ: một trình bổ trợ jQuery chuyển đổi hộp lựa chọn thành một loạt div vì đó là những gì nhà thiết kế muốn mà không biết đến 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 như vậy. Thay vào đó, hãy phân phối tất cả các mã đánh dấu từ phía máy chủ ở dạng hoàn chỉnh. Việc này cũng 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 – Đo điểm chuẩn các đoạn mã JavaScript nhỏ
  2. Firebug – Để lập hồ sơ trong Firefox
  3. Công cụ 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 DOM
  5. DynaTrace Ajax Edition – Dùng để phân tích và tối ưu hoá hoạt động vẽ trong Internet Explorer

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

  1. Google Speed
  2. Paul Irish về hiệu suất jQuery
  3. Hiệu suất JavaScript cực cao (Bản trình bày)