Cải thiện hiệu năng cho JavaScript Web Application chỉ trong vòng vài bước đơn giản

1785

JavaScript là một trong những ngôn ngữ lập trình phổ biến nhất thế giới. Được sử dụng cho web pages, software, mobile apps, ứng dụng trong console, etc.

JavaScript hiện diện ở khắp mọi nơi. Tuy nhiên, có nhiều ứng dụng sử dụng JavaScript gặp vấn đề về hiệu năng. Và trong bài viết hôm nay, chúng ta sẽ nói về cách tối ưu hóa các ứng dụng JavaScript.

Throttling & Debounce

Khi ta thêm event listener cho những hành động của user như scrolling, chúng ta thường bỏ qua một điều là listener sẽ tự hoạt động khi event được kích hoạt. Điều này có khả năng dẫn đến hiện tượng “nghẽn cổ chai” đối với các ứng dụng của Javascript.

Lấy scroll event làm ví dụ, khi chúng ta làm như sau:

function scrollHandler() {
    return console.log('yippeeeee');
}

window.addEventListener('scroll', scrollHandler());

Mỗi lần user scroll, “yippeeeee” sẽ logged vào console.

Cũng không có gì đáng lo đúng không? Nhưng đối với level hoạt động cao hơn như kiểm tra element trong viewport để ta có thể tạo animate, thì nó sẽ khá tốn kém memory cũng như thời gian xử lý chậm hơn.

Một cách đơn giản để sửa vấn đề trên là dùng debounce hoặc throttle trong function scrollHandle.

Nghe có vẻ phức tạp, nhưng:

Debouncing bảo đảm một function sẽ không bị gọi lại sau một khoảng thời gian.

Trong khi

Throttling giới hạn số lần gọi tối đa cho một junction trong một khoảng thời gian nhất định.

Như vậy, với ví dụ trên về scrolling, chúng ta sẽ debounce nhờ vào Lodash:

window.addEventListener('scroll', _.debounce(scrollHandler, 300));

// or

window.addEventListener('scroll', _.throttle(scrollHandler, 300));

Ngoài ra, tôi cũng khuyến khích bạn sử dụng library Underscore. Tất nhiên là bạn hoàn toàn có thể sử dụng một trong hai library ở trên hoặc là cứ lấy đoạn code function từ codebase mà dùng cho ứng dụng của mình.

Chuyển qua HTTP/2 và đừng Bundle

Trong khi HTTP/2 đang ngày càng phát triển và được biết đến nhiều hơn, thì việc kết hợp các JavaScript files thành một bundle lớn đã không còn cần thiết nữa.

HTTP/2 có chức năng của một multiplexed – nói cách khác nó được tạo ra để có thể thực hiện và quản lí nhiều file request cùng một lúc. HTTP/2 sử dụng chỉ một TCP connection để request nhiều file, không như HTTP/1 khi ta bắt buộc phải chia connection ra cho từng request riêng biệt. Nhờ đó mà HTTP/2 có tốc độ xử lý nhanh hơn hẳn.

Như vậy, việc đưa ra nhiều file lại là một việc khá tốt nếu bạn sử dụng HTTP/2. Tất nhiên là đừng quá nhiều (tham thì thâm mà).

Lược bỏ những thứ dư thừa

Nếu bạn sử dụng NPM để cài đặt các dependencies, thì hẳn bạn cũng nhận ra build file của mình chứa đầy rác. Theo như Guy Fieri đã nói, bạn sẽ không ngờ được file của mình nó chứa nhiều thứ vớ vẩn đến không ngờ.

Vì thế, thay vì phải thêm cả một lượng dữ liệu ngang với database chỉ để dùng một phần của chúng. Hãy lấy Lodash làm ví dụ, nó có rất nhiều function mà chúng ta có thể sẽ dùng tới, thay vì cứ giữ tất cả mọi thứ mà user có thể sẽ cần tới, thế thì tại sao không yêu cầu những gì chúng muốn.

Như thế này đây:

// Load the full build.
var _ = require('lodash');

// Load only the array helper.
var arr = require('lodash/array');

// Load only the debounce function.
var debounce = require('lodash/debounce');

Làm như trên sẽ giúp bạn giảm kích cỡ của file cũng như giúp tốc độc xử lí nhanh hơn.

Minify – Giảm thiểu

Là một trong những kĩ thuật tối ưu hóa trong Javascript được mọi người bàn tán nhất

Minification ám chỉ việc ta rút bỏ những yếu tố không cần thiết  và tính năng cồng kềnh trong Javascript Source.

Những yếu tố này bao gồm comments, semi-colons, whitespace etc. Ngoài ra giảm thiểu sự cồng kềnh bằng rút ngắn function và tên variable, giảm thiểu đồ dài của if-else và các thuật toán khác.

Với sự giúp đỡ của các tool như UglifyJs, Google Closure compiler hoặc online tools như JS Compress, JS minifier, việc sử dụng minify sẽ trở nên vô cùng dễ dàng.

Async & Defer

async và defer là hai attributes chúng ta có thể thêm vào script tags để giúp chúng load chạy không đồng bộ đến các page hoặc hoãn lại cho đến khi page đã load hết.

Lưu ý rằng async hoặc defer không block DOM khỏi rendering, và giúp cho ứng dụng chạy nhanh hơn

<!-- load meh.js asynchronously to the page -->
<script source="meh.js" async></script>

<!-- load meh.js after the page has loaded -->
<script source="meh.js" defer></script>

Recycle/Reduce the Dom

Các bạn có thấy rằng Facebook, Twitter, Instagram, 9gag và nhiều trang website khác có tính năng scrolling vô hạn không. Tuy nhiên, scrolling vô hạn sẽ khiến cho việc thêm

vào content nặng cho DOM, thì ta sẽ phải cần query nhiều yếu tố nữa.  Như vậy nó sẽ khiến hiệu năng bị giảm và ảnh hưởng tới tốc độ xử lí cũng như load của trình duyệt web bởi quá ăn RAM (tốn nhiều memory)

9gag có cách giải quyết vấn đề trên khá là hay. Sau khi user đã đọc được một số lượng meme nhất định, 9gags sẽ đưa ra một nút để bạn dùng để dọn đi những meme bạn đã đọc. Nếu bạn nhấn vào thì có thể thấy các meme trước đó sẽ bị xóa để những meme mới hiện ra và nhờ đó tốc độ load xử lý được cải thiện.

Cách giải quyết của 9gag rất tốt, nhưng bạn cũng có thể làm cách khác là tái chế lại các yếu tố của DOM. Nói cách khác, sau khi các yếu tố liên quan tới hiển thị hoàn thành nhiệm vụ thì sẽ được tái chế lại như ví dụ dưới đây:

requestAnimationFrame

Nhiều người cho rằng JavaScript animation khá chậm. Sự thật là nó chỉ chậm khi được lập trình một cách sơ sài. Nhưng ta có thể cải thiện điều đó.

Với requestAnimationFrame, chúng ta chỉnh định trình duyệt web gọi một function để render một frame. So với setTimeout, tốc độ được cải thiện nhiều lần nhờ vào việc kích hoạt GPU để có thể render hình ảnh nhanh nhất có thể.

var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

// Takes in the timestamp of the current frame
function step(timestamp) {
    if (!start) start = timestamp;

    // calculate total time passed
    var progress = timestamp - start;

    // get the number of pixels the element should move every frame
    element.style.left = Math.min(progress / 10, 200) + 'px';

    // only animate if time passed is lesser than 2000ms or 2 seconds
    if (progress < 2000) {
        window.requestAnimationFrame(step);
    }
}

window.requestAnimationFrame(step);

 

Nhìn thì có vẻ khá rắc rối chỉ để cho một animation đơn giản. Tuy nhiên nhờ vào các JavaScript libraries như Anime.js, Gsap mà công việc trên trở nên thật dễ dàng.

Dùng ở chế độ Offline

Một trong những tính năng của JavaScript service worker là cache file để dùng khi bạn offline. Hơn nữa, các file không chỉ cached để dùng lúc không có mạng mà còn có thể dùng nếu có mạng kết nối lại. Nhờ đó, ứng dụng có thể bỏ qua bước request JavaScript file mà lấy thẳng từ cache của nó vốn sẽ nhanh hơn nhiều lần so với lấy từ server.

Hãy dùng tới Promises

JavaScript promises sử dụng một API để miêu tả code và là một native function, chúng có khả năng tinh chỉnh và nên được dùng thường xuyên. Mặc khác, promises còn là asychronous (không đồng bộ), nên nó không bị block và sẽ cải thiện tốc độ xử lí cho ứng dụng.

Hãy dùng profiler cho code của bạn

Chrome developer tool cực kì mạnh mẽ với nhiều tool khác nhau. Ngoài console, DOM inspector, nó còn có cả profiler.

Còn được gọi là “Memory Panel”, profiler sẽ check và chạy nhiều bài test trên ứng dụng web của bạn để kiểm tra có bị memory leak (thất thoát bộ nhớ) không. Trong trường hợp có xảy ra, profiler sẽ phân tích và hiển thị trong một bản đồ thị, cho bạn biết ứng dụng có bị hiện tượng nghẽn cổ chai và memory leak hay không.

Lời kết

Tôi tin rằng nếu bạn làm theo những cách trên thì ứng dụng Javascript của bạn sẽ nhanh hơn hẳn. Và với sự phát triển của ngôn ngữ lập trình này, tin rằng vẫn có nhiều cách nữa mà bạn sẽ khám phá trong tương lai.

Nguồn: blog.topdev.vn via Scotch