Bạn gọi sort cho một mảng số rồi thấy 10 đứng trước 2. Sau đó, một biến khác cùng trỏ vào mảng cũng thay đổi dù không gọi sort trên biến đó. Đây là hai hành vi khác nhau cần giải thích: cách so sánh mặc định và việc sắp xếp tại chỗ.
Các ví dụ trong bài đã chạy bằng Node.js 24.18.1, tài liệu MDN được đối chiếu ngày 26/10/2026. Chúng chỉ kiểm tra ngữ nghĩa và kết quả của dữ liệu nhỏ; không có benchmark tốc độ hoặc cam kết một thuật toán sort cụ thể trong mọi engine.
1. Sort mặc định không suy ra ý định số học
Theo MDN về Array.sort, cách sắp xếp mặc định chuyển phần tử thành chuỗi rồi so sánh theo các đơn vị mã UTF-16. Sort cũng thay đổi mảng và trả lại cùng tham chiếu.
Lưu thành sort-demo.mjs rồi chạy bằng Node:
const numbers = [2, 10, 1];
const alias = numbers;
const result = numbers.sort();
console.log(numbers.join(','));
console.log(result === numbers, alias === numbers);
console.log(alias.join(','));
numbers.sort((a, b) => a - b);
console.log(numbers.join(','));
Output đã kiểm tra là 1,10,2, true true, 1,10,2, rồi 1,2,10. Comparator số học diễn đạt thứ tự tăng dần cần dùng; gán kết quả sort vào một biến mới không tạo ra bản sao để bảo vệ biến cũ.
Ví dụ chỉ dùng số hữu hạn đã biết. Nếu dữ liệu còn chứa string, null hoặc NaN, hãy xác định và kiểm tra kiểu trước khi áp dụng phép trừ. Một comparator ngắn chưa thay thế validation đầu vào.
2. Comparator phải trả một quan hệ nhất quán
Kết quả âm cho biết a đứng trước b, dương cho biết a đứng sau b, còn 0 cho biết bằng nhau theo tiêu chí đang xét. Trả boolean từ a > b không mô tả đầy đủ quan hệ đó:
const badCompare = (a, b) => a > b;
console.log(badCompare(1, 2), badCompare(2, 1));
Output là false true. Khi được dùng như kết quả số, cặp đó tương ứng 0 và 1 thay vì hai dấu đối nhau. Tài liệu sort giải thích comparator không đáp ứng hợp đồng có thể cho hành vi khác giữa các engine. Vì vậy bài không dùng một output sort từ comparator sai làm đáp án chung cho mọi JavaScript runtime.
Không sửa mảng, gọi request hay thay state bên ngoài trong comparator. Bạn không nên dựa vào số lần hoặc thứ tự engine gọi nó. Với quy tắc nhiều tiêu chí, viết chúng thành thứ tự rõ ràng rồi thử các cặp bằng nhau và đảo vị trí.
3. Dùng toSorted khi cần một mảng kết quả mới
Array.toSorted là biến thể trả mảng mới. Ví dụ sau đã được chạy trên Node 24.18.1:
const input = [2, 10, 1];
const ascending = input.toSorted((a, b) => a - b);
console.log(input.join(','));
console.log(ascending.join(','));
console.log(input === ascending);
Kết quả là 2,10,1, 1,2,10, rồi false. Nếu môi trường mục tiêu chưa hỗ trợ toSorted, có thể tạo một mảng mới trước khi sort theo cách phù hợp dự án. Cả hai cách tạo mảng mới đều không tự deep clone các object bên trong.
Với state của UI, điều bạn muốn thường là một giá trị mảng mới cho kết quả hiển thị mà không đổi thứ tự nguồn đang được nơi khác dùng. Hãy kiểm tra cả mảng nguồn lẫn mảng kết quả để tránh chỉ nhìn giao diện cuối cùng.
4. Tính ổn định và tiêu chí phụ
Trong JavaScript hiện đại, sort ổn định giữ thứ tự tương đối của các phần tử bằng nhau theo comparator. Tuy vậy, nếu yêu cầu sản phẩm là ưu tiên lớn trước rồi ID nhỏ trước, cần viết tiêu chí ID thay vì dựa vào thứ tự đầu vào tình cờ:
const tasks = [
{id: 2, priority: 5},
{id: 1, priority: 5},
{id: 3, priority: 1},
];
const byPriority = tasks.toSorted((a, b) => b.priority - a.priority);
const withTieBreak = tasks.toSorted(
(a, b) => b.priority - a.priority || a.id - b.id,
);
console.log(byPriority.map(x => x.id).join(','));
console.log(withTieBreak.map(x => x.id).join(','));
Output đã kiểm tra là 2,1,3, rồi 1,2,3. Trong bản đầu, hai mục priority bằng nhau giữ thứ tự nhập. Trong bản sau, phép so sánh ID quyết định thứ tự khi priority bằng nhau. Chỉ dùng ID dạng số ở đây vì đó là hợp đồng fixture.
Kiểm tra và lỗi thường gặp
Tạo ca có mảng rỗng, một phần tử, phần tử bằng nhau, thứ tự đã đúng và thứ tự đảo ngược. Với comparator nhiều tiêu chí, thêm ca hòa ở tiêu chí đầu. Xác nhận comparator trả 0 cho cùng một đối tượng và kết quả đối xứng dấu cho hai giá trị khác nhau trong phạm vi dữ liệu hợp lệ.
Đừng xem “trên máy tôi vẫn đúng” là bằng chứng comparator sai hợp đồng có thể dùng. Cũng đừng thay sort bằng toSorted rồi bỏ qua kiểu dữ liệu; nó đổi chính sách mutation, không tự sửa ý nghĩa so sánh.
Gợi ý trả lời phỏng vấn
Hãy trình bày thứ tự mặc định, tác động lên mảng gốc, vai trò comparator và cách xử lý giá trị bằng nhau. Sau đó chạy một fixture nhỏ để chứng minh từng ý. Một lời giải giải thích được hợp đồng sẽ hữu ích hơn việc chỉ nhớ (a, b) => a - b.

