Trong JavaScript, việc kiểm tra sự tồn tại của một giá trị trong tập hợp dữ liệu là thao tác thường xuyên. Set.has() nổi lên như một phương pháp hiệu quả hơn so với Array.includes(), đặc biệt khi xử lý dữ liệu lớn.
Đôi khi, hiệu suất trở thành chìa khóa hoặc ít nhất là một yếu tố quan trọng khi xây dựng ứng dụng, đặc biệt khi xử lý các tập dữ liệu lớn hoặc các yêu cầu thời gian thực. Một trong những tác vụ phổ biến nhất trong JavaScript là kiểm tra xem một giá trị nào đó có tồn tại trong một tập hợp hay không.
Hai lựa chọn thường được sử dụng nhất là Array.includes() và Set.has(). Cả hai đều hoạt động, nhưng thực tế, Set.has() hoạt động tốt hơn Array.includes(). Chúng ta hãy cùng tìm hiểu lý do tại sao và quyết định khi nào nên sử dụng phương án nào.
Hiểu Rõ Array.includes() và Set.has()
Có hai phương thức có vẻ khá đơn giản trong cách sử dụng, nhưng có cách triển khai khác nhau, đó là Array.includes() và Set.has().
1. Array.includes()
Phương thức includes() kiểm tra xem một giá trị nhất định có tồn tại trong một mảng hay không. Nó sử dụng độ phức tạp thời gian là O(n), do đó độ dài của mảng càng lớn thì thời gian kiểm tra giá trị càng lâu. Điều này là do Array.includes() tìm kiếm mảng từ đầu đến cuối (hoặc cho đến khi tìm thấy giá trị) và mảng càng lớn thì mất càng nhiều thời gian.
2. Set.has()
Phương thức has() của Set cũng kiểm tra xem một giá trị nhất định có tồn tại hay không nhưng thực hiện nhanh hơn nhiều. Set.has() dựa trên cấu trúc bảng băm cho phép tra cứu thời gian không đổi hoặc độ phức tạp thời gian O(1). Không giống như mảng, set được xây dựng để xử lý các giá trị duy nhất, vì vậy nó sẽ không có các giá trị trùng lặp bên trong và có thời gian tra cứu nhanh hơn.
Tại sao Set.has() nhanh hơn đối với tập dữ liệu lớn?
Khi bạn sử dụng Set.has(), JavaScript có thể tìm thấy mục trong một thao tác trực tiếp, bất kể có bao nhiêu mục trong set. Ví dụ, khi kiểm tra xem một giá trị có trong một set chứa một triệu phần tử, thời gian Set.has() tiêu tốn sẽ chính xác giống như kiểm tra mười phần tử.
Mặt khác, Array.includes() kiểm tra từng phần tử từ trái sang phải tuần tự cho đến khi tìm thấy mục quan tâm hoặc đến cuối cùng của nó. Điều đó có nghĩa là kích thước càng lớn thì thời gian kiểm tra càng lâu, đặc biệt là trong trường hợp mục gần cuối - và chắc chắn khi mục được đề cập không có ở đó.
Dưới đây là một ví dụ cụ thể:
const bigArray = Array.from({ length: 1000000 }, (_, i) => i);
const bigSet = new Set(bigArray); const valueToFind = 999999; // Array.includes (O(n)) - Slower for large arrays
console.time("Array.includes");
bigArray.includes(valueToFind);
console.timeEnd("Array.includes"); // Set.has (O(1)) - Faster for large sets
console.time("Set.has");
bigSet.has(valueToFind);
console.timeEnd("Set.has");
Khi bạn chạy đoạn mã này, bạn sẽ thấy rằng Set.has() vượt trội hơn Array.includes() trên các mảng lớn. Trên thực tế, sự khác biệt đó có thể chuyển thành hình ảnh động mượt mà hơn, thời gian tải nhanh hơn hoặc thậm chí sử dụng ít tài nguyên hơn trên máy chủ của bạn.
Khi nào bạn nên sử dụng Set.has() và Array.includes()?
Tất cả phụ thuộc vào mục tiêu bạn muốn đạt được. Sau đây là tóm tắt ngắn gọn:
1. Sử dụng Set.has() nếu:
- Bạn đang xử lý dữ liệu lớn và đang thực hiện nhiều lượt tra cứu.
- Bạn đang làm việc với các giá trị duy nhất, chẳng hạn như một tập hợp các ID người dùng, thẻ hoặc từ khóa duy nhất.
- Bạn không ngại chi phí chuyển đổi mảng thành set ban đầu nhỏ để giảm thiểu số lượng tra cứu sau này.
2. Sử dụng Array.includes() nếu:
- Tập dữ liệu của bạn nhỏ và sự khác biệt về hiệu suất là không đáng kể.
- Bạn chỉ cần kiểm tra một mục một lần hoặc một vài lần, vì vậy việc tạo Set sẽ không mang lại lợi ích gì.
- Bạn đang xử lý các bản sao, điều mà set không thể xử lý.
Ví dụ sử dụng
Hãy tưởng tượng bạn đang triển khai tính năng tìm kiếm người dùng, lọc tên dựa trên danh sách các từ bị chặn. Nếu bạn có hàng trăm từ bị chặn và đang tìm kiếm thường xuyên, việc sử dụng Set cho các từ bị chặn có thể giúp mỗi lần kiểm tra tìm kiếm nhanh hơn:
const blockedWords = new Set(["spam", "test", "fakeuser", "bot"]); // Small data, but scales well
const username = "fakeuser42"; if (blockedWords.has(username)) { console.log("Username is blocked");
} else { console.log("Username is allowed");
}
Ngay cả trong các trường hợp nhỏ hơn, Set sẽ giúp giữ mọi thứ hiệu quả và có thể dự đoán được. Thêm vào đó, nếu danh sách từ bị chặn tăng lên, bạn sẽ có sẵn một giải pháp có khả năng mở rộng.
Những điểm chính quan trọng trong bài viết này
- Hiệu suất: Set.has() cung cấp độ phức tạp thời gian O(1), giúp nó nhanh hơn nhiều so với Array.includes() với O(n) đối với các tập hợp lớn hơn.
- Tính phù hợp: Set được thiết kế cho các giá trị duy nhất, vì vậy nó được tối ưu hóa tự nhiên cho việc tra cứu. Mảng linh hoạt hơn với các giá trị trùng lặp nhưng chậm hơn trong việc kiểm tra sự tồn tại.
- Khả năng mở rộng: Khi dữ liệu của bạn tăng lên, Set.has() tiếp tục hoạt động tốt, trong khi Array.includes() sẽ chậm lại.
Kết luận
Điều quan trọng là phải biết kích thước và bản chất của tập dữ liệu của bạn khi lựa chọn giữa Set.has() và Array.includes(). Array.includes() hoàn toàn phù hợp cho các tập dữ liệu nhỏ, nhưng Set.has() là một công cụ quý giá trong các tình huống mà tốc độ là rất quan trọng.
Cấu trúc dữ liệu phù hợp có thể giúp tối ưu hóa các ứng dụng JavaScript của bạn để nhanh hơn và hiệu quả hơn mà không cần thực hiện các thay đổi lớn đối với mã của bạn.
Do đó, bất cứ khi nào bạn thấy mình đang kiểm tra xem thứ gì đó có ở đó hay không, thì bạn nên tự hỏi bản thân: Đó có phải là công việc cho một mảng hay tôi có thể sử dụng sức mạnh của một set? Việc lựa chọn đúng có thể là sự khác biệt mà ứng dụng của bạn cần.
Cảm ơn các bạn đã theo dõi!