NỘI DUNG BÀI VIẾT
1. Mở đầu
Xuất thân từ kẻ chuyên code PHP, khi lần đầu tiên tôi thấy mọi người sử dụng forEach() để duyệt qua các mảng của họ, bản thân tôi không nghĩ gì về nó – đó là cách triển khai chính xác của một tiêu chuẩn cho vòng lặp, tôi tự nhủ. Bởi forEach() sinh ra để làm như vậy. Sau khi viết một số mã JavaScript, tôi nhanh chóng nhận ra rằng cả hai có sự khác biệt.
Trong bài viết này, tôi muốn phác thảo sự khác biệt đằng sau tiêu chuẩn cho vòng lặp và phương thức forEach() cũng như nhận xét về một số lợi ích mà mỗi phương thức mang lại.
Các bạn đọc bài viết vui lòng không hiểu tiêu đề theo nghĩa đen. Mục tiêu của tôi với bài viết là thông báo cho người đọc về các “nút thắt cổ chai” và cung cấp thông tin chi tiết về thời điểm bạn có thể muốn hoặc không muốn sử dụng forEach(). Bắt đầu nào!
2. forEach hoạt động như thế nào?
Phương thức forEach chấp nhận một hàm callback làm đầu vào và đối với mỗi phần tử trong mảng, hàm callback này sẽ được thực thi. Hàm forEach cũng cung cấp một đối số tùy chọn được xác định trong hàm callback.
Kết quả:
corgis - 0
are - 1
cool - 2
Short-Circuiting
Nếu bạn không biết Short-Circuiting (đoản mạch) là gì thì tôi sẽ giải thích. Nó đề cập đến thời điểm chúng ta kết thúc sớm hoặc bỏ qua một lần lặp của vòng lặp. Khi chúng ta sử dụng forEach(), không có cách nào để tận dụng hiện tượng đoản mạch,trong mọi trường hợp của vòng lặp, chúng ta sẽ phải chịu thời gian chạy tuyến tính đối với kích thước mảng.
Tại sao tôi nên quan tâm đến điều này? Hãy tưởng tượng chúng ta có một mảng chưa được sắp xếp gồm 1 tỷ phần tử và chúng ta muốn tìm một phần tử nhất định. Giả sử thực sự may mắn và phát hiện phần tử này trong lần lặp đầu tiên.
Trên thực tế, chúng ta muốn quay trở lại sớm vì nó đã tìm thấy những gì chúng ta đang tìm kiếm, nhưng theo cách forEach() được triển khai, nó sẽ luôn chạy qua các phần tử còn lại. Chúng ta có thể sẽ sử dụng phương thức findIndex() cho loại vấn đề này.
Hiệu suất
Trong phương thức forEach(), vì chúng ta đang gọi một hàm callback ở mỗi lần lặp nên ta đang tạo thêm tài nguyên dẫn đến tốc độ chậm hơn so với vòng lặp for truyền thống. Khi so sánh với vòng lặp for truyền thống, chúng ta có một câu lệnh khởi tạo, một điều kiện được đánh giá ở mỗi lần lặp và một đoạn mã thực thi sau khi phần thân của vòng lặp được tăng lên. Liên quan đến phương thức forEach(), nơi chúng ta phải tạo các lệnh gọi hàm bổ sung ở mỗi lần lặp, nó sẽ có chi phí thấp hơn.
Để kiểm tra hiệu suất, tôi đã tạo một tập lệnh hẹn giờ theo dõi thời gian thực thi sau khi khởi tạo một mảng. Cả hai vòng đều đang thực hiện một phép toán O(1) đơn giản trong phần thân của chúng.
Khả năng đọc được
Khi phát triển phần mềm, việc tạo mã có thể bảo trì và đọc được là ưu tiên hàng đầu. Tôi tin rằng việc tiếp tục sử dụng forEach() sẽ khiến mã dễ đọc hơn rất nhiều.
Cũng cần lưu ý rằng các trường hợp mà kích thước đầu vào của bạn cực kỳ lớn, như trong ví dụ trên, có xu hướng khó xảy ra. Ở kích thước đầu vào hợp lý, hai vòng lặp hoạt động tương đối giống nhau. Bạn có muốn có một chức năng hoạt động nhanh hơn vài mili giây, ít tốn tài nguyên mà lại dễ đọc không?
Kết luận
Các nâng cấp được phát hành với các phiên bản cuối cùng của ECMAScript đã nâng cao tiêu chuẩn cho JavaScript nói chung. Tôi là người tin tưởng nhất vào việc sử dụng đúng công cụ cho công việc. Nếu bạn cần duyệt một số lượng lớn các phần tử trong một mảng thì có thể vòng lặp for truyền thống là giải pháp cho vấn đề của bạn.
Nếu không, tôi không thấy vấn đề gì ngoài việc chút hiệu suất khi sử dụng forEach() hoặc bất kỳ phương thức mảng ES nào được phát hành sau này. Sự cân bằng giữa khả năng đọc và hiệu suất là điều tôi muốn ưu tiên trong hầu hết các tình huống.
Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.
Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.