HomeLập trìnhJavaScriptCẩn thận với...

Cẩn thận với các phương thức mảng xâu chuỗi trong JavaScript


bởi Balaganesh Damodaran

Lớp Array của JavaScript hiển thị khá nhiều phương thức (bộ lọc, ánh xạ, thu nhỏ), các phương thức này lặp qua một mảng và gọi một hàm lặp để thực hiện các hành động trên mảng. Chuỗi các phương thức này cho phép bạn viết mã rõ ràng, dễ đọc. Nhưng sự tiện lợi này khiến chúng ta phải trả giá như thế nào về hiệu suất và nó có đáng không?

4iKtwVSXgNmeSNOpUi2enoqqLfGHOsV8ezhB

Javascript là một ngôn ngữ ‘chức năng’. Điều này có nghĩa là các hàm là các đối tượng hạng nhất trong Javascript và do đó chúng có thể được truyền dưới dạng tham số cho các hàm khác. Có khá nhiều phương thức tích hợp được cung cấp bởi thư viện tiêu chuẩn Javascript, sử dụng thực tế này để cho phép chúng tôi viết mã rõ ràng, dễ hiểu và dễ đọc.

Các phương thức mảng Javascript tích hợp và xâu chuỗi

Một lớp tích hợp như vậy giúp sử dụng rộng rãi bản chất chức năng của Javascript là Array lớp. Arrays trong Javascript hiển thị một số phương thức cá thể, trong đó:

  • chấp nhận một chức năng như một đối số,
  • lặp lại trên mảng,
  • và gọi hàm, chuyển qua mục mảng dưới dạng tham số cho hàm.

Tất nhiên, phổ biến nhất trong số này là forEach, filter, mapreduce. Vì một số phương thức này cũng trả về Array ví dụ như giá trị trả về của phương thức, chúng thường được kết nối với nhau như thế này:

const tripExpenses = [{    amount: 12.07,    currency: 'USD',    paid: true}, {    amount: 1.12,    currency: 'USD',    paid: true}, {    amount: 112.00,    currency: 'INR',    paid: false}, {    amount: 54.17,    currency: 'USD',    paid: true}, {    amount: 16.50,    currency: 'USD',    paid: true}, {    amount: 189.50,    currency: 'INR',    paid: false}];
const totalPaidExpensesInINR = tripExpenses    .filter(expense => expense.paid)    .map(expense => {        if(expense.currency == 'USD')            return expense.amount * 70;        else            return expense.amount;    })    .reduce((amountA, amountB) => amountA + amountB);

Trong ví dụ này, chúng tôi đang tính toán tổng chi phí đã thanh toán sau khi chuyển đổi chúng từ USD sang INR. Để làm điều này, chúng tôi là:

  • filtering tripExpenses chỉ trích xuất các chi phí đã thanh toán,
  • mapping số tiền chi phí từ đơn vị tiền tệ được chỉ định và chuyển đổi nó thành INR, và
  • reducenhập số tiền INR để nhận tổng.
Đọc thêm  Cách chuyển đổi bảng HTML tĩnh thành lưới dữ liệu JavaScript động

Trông giống như một trường hợp sử dụng phổ biến, rất điển hình, hợp lệ để xâu chuỗi các phương thức mảng phải không? Rất nhiều nhà phát triển đã được dạy cách viết Javascript chức năng sẽ đưa ra một thứ gì đó tương tự khi được yêu cầu giải quyết vấn đề này.

Vấn đề với chuỗi phương thức mảng

Hiện nay, của chúng tôi tripExpenses mảng chỉ có 6 mục, vì vậy việc này tương đối nhanh. Nhưng điều gì sẽ xảy ra khi chúng ta phải phân tích chi phí chuyến đi cho toàn bộ nhân viên của công ty trong cả năm tài chính và tripExpenses mảng bắt đầu có hàng trăm nghìn phần tử?

Nhờ JSPerf, chúng ta có thể hình dung chi phí này khá dễ dàng. Vì vậy, hãy chạy thử nghiệm so sánh cho cùng một mã với tripExpenses có 10 phần tử, 10.000 phần tử và 100.000 phần tử. Đây là kết quả so sánh JSPerf:

oMRqEBBgNA9IHRWGLEzakbXFEMcL2Gfb2gyv

Biểu đồ hiển thị số lượng hoạt động mỗi giây và cao hơn là tốt hơn. Mặc dù tôi mong đợi trường hợp 100.000 phần tử hoạt động kém, nhưng tôi thực sự không mong đợi trường hợp 10.000 phần tử hoạt động kém. Vì nó không thực sự hiển thị trên biểu đồ, hãy xem các con số:

  • 10 phần tử — 6.142.739 thao tác mỗi giây
  • 10.000 phần tử — 2.199 thao tác mỗi giây
  • 100.000 Phần tử — 223 thao tác mỗi giây

Rất tiếc, điều đó thực sự tồi tệ! Và mặc dù việc xử lý một mảng gồm 100.000 phần tử có thể không xảy ra thường xuyên, nhưng 10.000 phần tử là một trường hợp sử dụng rất hợp lý mà tôi đã thấy thường xuyên trong nhiều ứng dụng mà tôi đã phát triển (chủ yếu ở phía máy chủ).

Điều này cho chúng ta thấy rằng khi chúng ta viết — ngay cả những gì có vẻ là mã khá đơn giản — chúng ta thực sự nên đề phòng mọi vấn đề về hiệu suất có thể nảy sinh do cách chúng ta viết mã.

Nếu, thay vì xâu chuỗi filter, mapreduce các phương thức cùng nhau, chúng tôi viết lại mã của mình sao cho tất cả công việc được thực hiện nội tuyến, trong một vòng lặp duy nhất, chúng tôi có thể đạt được hiệu suất tốt hơn đáng kể.

let totalPaidExpensesInINR = 0;
for(let expense of tripExpenses){    if(expense.paid){        if(expense.currency == 'USD')            totalPaidExpensesInINR += (expense.amount * 70);        else            totalPaidExpensesInINR += expense.amount;    }}

Hãy chạy một phép so sánh JSPerf khác để xem tính năng này hoạt động như thế nào so với bản sao chức năng của nó, trong thử nghiệm 10.000 phần tử:

Đọc thêm  Khóa JavaScript trong đối tượng – Cách kiểm tra xem một đối tượng có Khóa trong JS không
qMXDUV7I2B1K8LguRGP6BwmcPSeG59PeuxaL

Như bạn có thể thấy, trên Chrome (và theo tiện ích mở rộng Node.JS), ví dụ về chức năng chậm hơn tới 77% so với ví dụ về chức năng. Trên Firefox, các con số gần hơn rất nhiều, nhưng ví dụ về chức năng vẫn chậm hơn 16% so với ví dụ về ví dụ.

Tại sao lại có Đồng bằng hiệu suất lớn như vậy?

Vậy tại sao ví dụ chức năng lại chậm hơn nhiều so với ví dụ for-of? Chà, đó là sự kết hợp của nhiều yếu tố, nhưng những yếu tố chính mà với tư cách là nhà phát triển, chúng tôi có thể kiểm soát từ vùng đất của người dùng là:

  • Lặp lại các phần tử mảng giống nhau nhiều lần.
  • Chi phí chung của các cuộc gọi hàm cho mỗi lần lặp trong ví dụ về chức năng.

Nếu bạn xem ví dụ for-of, bạn sẽ thấy rằng chúng ta chỉ lặp qua tripExpenses mảng một lần. Ngoài ra, chúng tôi không gọi hàm nào từ bên trong, thay vào đó thực hiện các tính toán của chúng tôi nội tuyến.

Một trong những ‘chiến thắng’ về hiệu suất lớn mà các công cụ Javascript hiện đại có được là bằng cách gọi hàm nội tuyến. Điều này có nghĩa là công cụ sẽ thực sự biên dịch mã của bạn thành một phiên bản trong đó trình biên dịch thay thế lời gọi hàm, bằng chính hàm đó (tức là nội tuyến nơi bạn gọi hàm). Điều này giúp loại bỏ chi phí gọi hàm và mang lại hiệu suất rất lớn.

Tuy nhiên, chúng tôi không phải lúc nào cũng có thể nói chắc chắn liệu một công cụ Javascript có chọn nội tuyến một chức năng hay không, do đó, việc tự mình thực hiện sẽ đảm bảo rằng chúng tôi có hiệu suất tốt nhất có thể.

Đọc thêm  Thuật toán cây tìm kiếm nhị phân cho người mới bắt đầu JavaScript

Phần kết luận

Một số nhà phát triển có thể coi ví dụ for-of khó đọc hơn và khó hiểu hơn ví dụ chức năng. Đối với ví dụ cụ thể này, tôi muốn nói rằng cả hai kiểu đều có thể đọc được như nhau. Tuy nhiên, trong trường hợp của ví dụ chức năng, sự tiện lợi của chuỗi phương thức có xu hướng che giấu nhiều lần lặp lại và lệnh gọi hàm khỏi nhà phát triển, do đó giúp nhà phát triển thiếu kinh nghiệm dễ dàng viết mã không hiệu quả.

Tôi không nói rằng bạn phải luôn luôn tránh cách thức chức năng – tôi chắc rằng có rất nhiều trường hợp hợp lệ để sử dụng cách thức chức năng và xâu chuỗi các phương thức. Nhưng một nguyên tắc chung cần nhớ khi nói đến hiệu suất và lặp mảng trong Javascript là nếu bạn đang xâu chuỗi các phương thức lặp qua toàn bộ mảng, có lẽ bạn nên dừng lại và xem xét tác động hiệu suất trước khi tiếp tục.

Tôi muốn nghe ý kiến ​​​​của bạn về những gì tôi đã viết trong bài viết này. Đừng kêu vang với ý kiến ​​​​của bạn dưới đây.

[Feb 6th, 2019] Một số cảnh báo và những điều cần lưu ý, như được chỉ ra bởi những người bình luận

Như Paul B đã chỉ ra, có một vấn đề về hiệu suất khi sử dụng `for…of` ở dạng được phiên mã trong trình duyệt, nhưng bạn luôn có thể sử dụng vòng lặp for bình thường với một biến lặp để giải quyết vấn đề này. Tuy nhiên, như Paul nói, có khá nhiều lợi thế khi gắn bó với hàm lặp. Hãy đọc bình luận của anh ấy, nó xứng đáng là một bài báo.

Ngoài ra, nhiều người cũng đã nói rằng đây sẽ là tối ưu hóa sớm hoặc tối ưu hóa vi mô và tôi đồng ý một phần với họ. Nói chung, bạn nên luôn tối ưu hóa khả năng đọc và khả năng bảo trì so với hiệu suất, cho đến khi hiệu suất kém thực sự bắt đầu ảnh hưởng đến bạn. Khi bạn đã đạt đến điểm đó, thì bạn có thể muốn xem xét lại các trình vòng lặp của mình.

Được xuất bản lần đầu tại sleepysamurai.com vào ngày 8 tháng 1 năm 2019.



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status