7 mẹo để viết code JavaScript hiệu quả

29 tháng 05, 2021 - 364 lượt xem

Một sai lầm phổ biến là tập trung vào làm việc mà quên đi hiệu suất và hiệu quả. Đó là sự khác biệt chính giữa một nhà phát triển cấp dưới và một nhà phát triển cấp cao. Khi viết JavaScript, chúng ta luôn cần để ý đến hiệu suất. Chúng tôi muốn ứng dụng của mình chạy hiệu quả nhất có thể trên bất kỳ thiết bị nào có thể có quyền truy cập vào ứng dụng đó. Chúng tôi không thể cho rằng ứng dụng web của chúng tôi luôn được thực thi trên các thiết bị mạnh mẽ.

Chúng tôi có thể làm gì để cải thiện hiệu suất JavaScript của mình? Hôm nay, chúng tôi sẽ có một số mẹo sẽ giúp bạn tăng hiệu suất tổng thể của ứng dụng web của mình. Chúng sẽ ngăn mã của bạn tạo ra nút thắt cổ chai và đảm bảo rằng trải nghiệm người dùng chất lượng cao được cung cấp cho người dùng.

1. Quan tâm đến LocalStorage và SessionStorage

LocalStorage và SessionStorage là những tính năng tuyệt vời cho phép chúng tôi duy trì dữ liệu trong ứng dụng web của mình. Sự khác biệt giữa hai là gì? Dữ liệu SessionStorage sẽ bị xóa khi phiên trang kết thúc.

“Các cơ chế này có sẵn thông qua các thuộc tính Window.sessionStorage và Window.localStorage (nói chính xác hơn,trong việc hỗ trợ các trình duyệt, đối tượng Window triển khai các đối tượng WindowLocalStorage và WindowSessionStorage, mà thuộc tính localStorage và sessionStorage bị treo) - việc gọi một trong số này sẽ tạo ra một thể hiện của đối tượng Storage, qua đó các mục dữ liệu có thể được thiết lập, truy xuất và loại bỏ. Một đối tượng Storage khác được sử dụng cho sessionStorage và localStorage cho mỗi nguồn gốc - chúng hoạt động và được kiểm soát riêng biệt. ” - Tài liệu Web MDN

Tốt và hữu ích như chúng có, các API web đó là đồng bộ. Điều đó nghĩa là gì? Nó có nghĩa là nếu bạn không cẩn thận về nó, bạn có thể làm chậm ứng dụng của mình. Các API đó sẽ giữ luồng chính. LocalStorage và SessionStorage chỉ có thể lưu trữ các chuỗi. Điều đó có nghĩa là việc lưu trữ các đối tượng lớn hoặc phức tạp sẽ ảnh hưởng đến hiệu suất, vì bạn sẽ cần sử dụng các hoạt động JSON.stringify và JSON.parse.

Hãy xem một ví dụ:

const startTime = performance.now();

const testData = Array(1000).fill(true);

// store data
localStorage.setItem('foo', JSON.stringify(testData));

// retrieve data
const storedData = JSON.parse(localStorage.getItem('foo'));

const endTime = performance.now();

console.log(`time:  ${endTime - startTime} ms.`);
// time between 0.15ms and 0.2ms

 

Trong bài kiểm tra này, tôi có thể trải nghiệm thời gian thực thi 0,15-0,20ms trên MacBook Pro 2017. Để nhìn vào phối cảnh: đây là khoảng thời gian mà một hoạt ảnh khung hình cần có. Chúng tôi có thể kết luận rằng đó là khá lâu.

Các giải pháp khả thi là gì? Đây là một số ý tưởng:

  • Chỉ lưu trữ lượng dữ liệu tối thiểu cần thiết.
  • Tạo sự trừu tượng của riêng bạn cho nó. Bạn có thể truy cập vào bộ nhớ cache để truy cập vào nó, giúp truy cập thông tin đã được lấy nhanh hơn.

 

// dummy example do not use in prod
const cache = {};

export const getStorageValue = (key) => {
    if (cache[key]) {
        return cache[key];
    }

    const value = localStorage.getItem(key);
    return value === null ? value : JSON.parse(value);
};

export const setStorageValue = (key: string, value: unknown) => {
    cache[key] = value;
    localStorage.setItem(key, JSON.stringify(value));
};

 

  • Suy nghĩ về các cách thay thế để lưu trữ dữ liệu. Ví dụ, bạn có thể sử dụng cookie để thay thế. Nó phụ thuộc vào mọi tình huống cụ thể.

2. Sử dụng API Web Observer

Việc cố gắng liên tục theo dõi các sự kiện từ trình duyệt có thể có tác động đáng kể đến hiệu suất trên ứng dụng web của chúng tôi. Điều đó đặc biệt đáng chú ý khi nghe các sự kiện scrolling.

Giả sử bạn muốn kích hoạt một số hành động khi một phần tử hiển thị trên màn hình. Hầu hết các nhà phát triển sẽ kết nối với sự kiện scroll và sau đó cố gắng tìm hiểu xem phần tử có hiển thị trên màn hình hay không. Điều đó là cực kỳ kém hiệu quả. Làm thế nào chúng ta có thể làm điều đó khác nhau?

Chúng ta có thể sử dụng tính năng IntersectionObserver. Nó cho phép chúng ta quan sát các yếu tố một cách phản ứng. Chúng tôi sẽ nhận được thông báo sự kiện khi các yếu tố đó hiển thị hoặc ẩn trên chế độ xem.

Đó chỉ là một ví dụ. Hãy xem danh sách đầy đủ các API Web quan sát mà chúng tôi có hỗ trợ gốc cho:

  • IntersectionObserver: Báo cáo khả năng hiển thị của quảng cáo để tính toán doanh thu quảng cáo.
  • MutationObserver: Khả năng theo dõi các thay đổi được thực hiện đối với cây DOM.
  • ResizeObserver: Báo cáo các thay đổi đối với kích thước của nội dung của Element's hoặc hộp viền hoặc hộp bao quanh của SVGElement.

Hỗ trợ trình duyệt cho tất cả những điều này là tốt và có nhiều polyfills tốt cho các trình duyệt cũ hơn.

3. Nắm bắt kỹ thuật Return Early

Chúng tôi đã quen với mô hình if / else và chưa bao giờ thực sự thắc mắc về nó.

Tuy nhiên, qua kinh nghiệm, bạn nhận ra rằng một đoạn mã có đầy đủ if và else là:

  • Không hiệu quả
  • Khó để đọc
  • Khó duy trì

Chúng ta có thể làm gì để cải thiện điều đó? Chúng ta có thể nắm lấy return early pattern.

“Return early” là một mẫu mà bạn nên trả lại kết quả sớm nhất có thể thay vì các cách sử dụng báo cáo else.

Hãy làm một ví dụ FizzBuzz cổ điển. Đoạn mã dưới đây có thể là một giải pháp:

 

function FizzBuzz(i) {
    let result = undefined;
    if (i % 15 == 0) {
        result = 'FizzBuzz';
    } else if (i % 3 == 0) {
        result = 'Fizz';
    } else if (i % 5 == 0) {
        result = 'Buzz';
    } else {
        result = i;
    }
    return result;
}

 

Nếu chúng tôi áp dụng mô hình return early pattern, chúng tôi nhận được code bên dưới:

 

function FizzBuzz(i) {
    if (i % 15 == 0) {
        return 'FizzBuzz';
    }
    if (i % 3 == 0) {
        return 'Fizz';
    }
    return  (i % 5 == 0) ? 'Buzz' : i;
}

Bằng cách áp dụng mô hình return early pattern, mã của chúng tôi đã trở nên:

  • Nhiều hơn
  • Dễ đọc hơn
  • Hiệu quả hơn

4. Master Async và Defer

Cách bạn tải các tập lệnh trên trang web của mình là rất quan trọng. Nó đóng một vai trò lớn trong lộ trình kết xuất quan trọng. Sau khi người dùng điều hướng đến trang web của bạn, có một cuộc chạy đua với thời gian để cung cấp nội dung sớm nhất có thể. Đối với vấn đề đó, điều quan trọng là phải hiểu cách bạn có thể tải các tập lệnh theo cách hiệu quả nhất.

Bạn có ba lựa chọn:

  • default: Tập lệnh được tìm nạp và thực thi, trong khi quá trình phân tích cú pháp vẫn tạm dừng cho đến khi quá trình thực thi kết thúc.
  • async: Khi có thuộc tính async, tập lệnh sẽ được tìm nạp song song với việc phân tích cú pháp và đánh giá ngay khi sẵn sàng. Thứ tự khi kịch bản được thực thi sau đó là không thể đoán trước.
  • defer: Khi thuộc tính này xuất hiện, nó cho trình duyệt biết rằng tập lệnh được thực thi sau khi tài liệu đã được phân tích cú pháp, nhưng trước khi kích hoạt DOMContentLoaded. Trái ngược với async, các tập lệnh này sẽ thực thi theo thứ tự mà chúng xuất hiện trong tài liệu.

Điều rất quan trọng là phải hiểu sự khác biệt giữa ba cách này để tải các tập lệnh.

Theo quy tắc chung, các tập lệnh không có phụ thuộc sẽ được thực thi với thuộc tính async, trong khi các tập lệnh khác phải được đánh dấu bằng thuộc tính defer.

5. Trì hoãn các Non-Essential Task

Các ứng dụng JavaScript phức tạp thường có rất nhiều code chạy liên tục. Tuy nhiên, có thể có một số nhiệm vụ không cần phải thực hiện ngay lập tức. Những gì chúng tôi có thể làm cho những tác vụ có mức độ ưu tiên thấp đó là hoãn chúng lại một lúc khi trình duyệt không bận như vậy. Giải phóng trình duyệt của bạn khỏi những tác vụ đó sẽ dẫn đến việc thực thi hiệu quả hơn.

Làm thế nào mà có thể được thực hiện? Bằng cách sử dụng phương thức requestIdleCallback của trình duyệt.

“Phương thức window.requestIdleCallback () xếp hàng đợi một hàm được gọi trong thời gian nhàn rỗi của trình duyệt. Điều này cho phép các nhà phát triển thực hiện công việc nền và công việc có mức độ ưu tiên thấp trên vòng lặp sự kiện chính mà không ảnh hưởng đến các sự kiện quan trọng về độ trễ như hoạt ảnh và phản hồi đầu vào ”. - Tài liệu Web MDN

Có nhiều tình huống mà bạn có thể áp dụng chiến lược này. Một trong những cách phổ biến nhất là gửi thông tin phân tích. Chúng tôi không phiền nếu dữ liệu đến sớm hay muộn. Đây là một nhiệm vụ không quan trọng vì nó sẽ được xử lý bởi một dịch vụ khác một cách không đồng bộ.

Hãy xem một ví dụ sử dụng:

const handleId = window.requestIdleCallback(() => {
  Analytics.sendEvent('purchaseProduct'....);
}, {
  timeout: 2000
});

 

Trong ví dụ này, chúng tôi sẽ đợi trình duyệt không hoạt động để thực thi mã Analytics của chúng tôi. Nếu điều đó không xảy ra trong vòng hai giây tới, nó vẫn sẽ được thực thi. Tùy chọn timeout là tùy chọn. Nó giúp bạn tùy chỉnh thêm việc thực thi code của mình. Nếu tùy chọn đó không có, nó sẽ chỉ được thực thi khi trình duyệt có thời gian cho nó.

Việc callback có thể bị hủy bằng cách sử dụng phương thức hủy IdleCallback ().

6. Tối ưu hóa Ảnh động

Ảnh động là một tính năng rất thú vị khi được thực hiện đúng. Chúng cung cấp ngữ cảnh cho người dùng về nơi mọi thứ đến và họ đang hướng đến. Tính hữu dụng của chúng không thể được phóng đại đủ khi thực hiện đúng.

Tuy nhiên, khi làm sai, chúng có thể có tác động tiêu cực nghiêm trọng đến hiệu suất. Hoạt ảnh JavaScript tùy chỉnh có thể chậm chạp và làm hỏng trải nghiệm người dùng của bạn. Điều đó đặc biệt đáng chú ý trên các ứng dụng React Native, nơi mà cầu nối JavaScript là một nút thắt cổ chai lớn.

Tại sao không dựa vào trình duyệt để thực hiện công việc nặng nhọc cho chúng ta? Hoạt ảnh CSS chạy theo kiểu hiệu quả hơn. Khung hoạt hình CSS cũng có thể truy cập thông qua JavaScript nhờ API hoạt ảnh web mới.

Tuy nhiên, nếu bạn cần tạo một số hoạt ảnh tùy chỉnh, vui lòng tránh sử dụng setTimeout:

setTimeout(() => {
  triggerAnimation();
}, 16.6)

 

SetTimeoutcallback sẽ không chạy sau mỗi 16,6 mili giây. Thời gian chờ chỉ là ước tính. Thay vào đó, bạn nên sử dụng requestAnimationFrame.

“Phương thức window.requestAnimationFrame () cho trình duyệt biết rằng bạn muốn thực hiện hoạt ảnh và yêu cầu trình duyệt gọi một hàm được chỉ định để cập nhật hoạt ảnh trước lần sơn lại tiếp theo. Phương thức này nhận một callback làm đối số được gọi trước khi sơn lại.” - Tài liệu Web MDN

Hãy viết lại ví dụ trên:

function step(timestamp) {
  ...
  if (shouldContinue) {
    window.requestAnimationFrame(step);
  }
}
window.requestAnimationFrame(step);

 

Bây giờ phương thức hoạt ảnh của chúng tôi sẽ được chạy trước mỗi chu kỳ sơn. Công việc của bạn bây giờ là tập trung vào việc có một mã hoạt ảnh hiệu quả.

7. Theo dõi Memory

JavaScript không cung cấp bất kỳ nguyên tắc quản lý bộ nhớ nào. Thay vào đó, bộ nhớ được quản lý bởi JavaScript VM thông qua một quá trình thu hồi bộ nhớ. Quá trình đó được gọi là garbage collection.

Rò rỉ bộ nhớ là gì? Đó là một phần bộ nhớ được cấp phát mà phần mềm không thể lấy lại.

JavaScript không tự làm rò rỉ bộ nhớ. Thay vào đó, nguyên nhân là do việc lưu giữ bộ nhớ không chủ ý từ phía nhà phát triển. Miễn là code gọn gàng và chúng tôi không quên tự dọn dẹp, thì sẽ không có rò rỉ nào xảy ra.

Bộ nhớ bị rò rỉ có thể có tác động thực sự lớn đến hiệu suất ứng dụng web của chúng tôi. Mã sẽ tiêu hao tài nguyên máy tính của người dùng. Bộ nhớ đã được sử dụng càng nhiều, trình thu gom rác sẽ chạy càng lâu và càng thường xuyên. Điều đó làm giảm khả năng phản hồi và tương tác của trang web của chúng tôi, làm hỏng trải nghiệm người dùng.

Những lĩnh vực chính mà chúng ta phải lưu ý là gì?

  • Event listeners: Đảm bảo rằng tất cả các bộ hẹn giờ đã được xóa khỏi tài liệu khi không cần thiết.
  • Timer listeners: Đảm bảo rằng tất cả các khoảng thời gian và thời gian chờ được hủy bỏ khi chúng không cần thiết.
  • Observers: Đảm bảo rằng bất kỳ người quan sát nào cũng được ngắt kết nối khi không cần thiết.
  • Window object: Đảm bảo rằng chúng tôi không tăng mức sử dụng bộ nhớ toàn cầu của mình.

Lời kết:

Chúng tôi đã thấy rất nhiều nơi mà chúng tôi có thể cải thiện hiệu suất JavaScript của mình. Bạn cần chọn tất cả các boxes này để mã của bạn hoạt động hiệu quả:

  • Sử dụng hợp lý các khả năng lưu trữ
  • Quản lý tốt bộ nhớ
  • Dựa vào API của trình duyệt để quản lý trạng thái của các phần tử
  • Tải các scripts
  • Tạo hoạt ảnh trên CSS càng nhiều càng tốt
  • Hạn chế logic if / else
  • Trì hoãn mã JavaScript không cần thiết

Tôi hy vọng những mẹo này sẽ giúp ứng dụng của bạn chạy hiệu quả hơn.

Nguồn bài viết tại đây.

Bình luận

avatar
Bùi Văn Hiên 2021-05-29 12:31:53.279388+00

cảm ơn tác giả, có nhiều thứ chưa biết quá =))

Avatar
* Vui lòng trước khi bình luận.
Ảnh đại diện
 0 Thích
0