0

Tất Tần Tật Về Interface Array: Đọc Hiểu File Định Nghĩa Core Để Làm Chủ 22 Phương Thức Mảng Trong TypeScript

Chào anh em Viblo! 👋

Nếu có một cấu trúc dữ liệu mà anh em dùng nhiều nhất mỗi ngày, bất kể là làm Frontend hay Backend, thì đó chắc chắn phải là Mảng (Array). Thao tác với mảng trong JavaScript/TypeScript thì ai cũng biết, nhưng đã bao giờ anh em mở file định nghĩa kiểu mẫu (d.ts) gốc của ngôn ngữ ra để xem các kỹ sư Microsoft định nghĩa interface Array<T> như thế nào chưa?

Việc đọc hiểu tường tận giao diện này không chỉ giúp anh em viết code chuẩn Type-safe, mà còn giúp lột trần cách các phương thức vận hành bên dưới lớp cánh gà: Phương thức nào làm thay đổi mảng gốc (Mutate)? Phương thức nào làm chậm hệ thống? Phương thức nào là vũ khí tối thượng để xử lý data?

Hôm nay, mình sẽ cùng anh em "bẻ khóa" toàn bộ các thuộc tính và phương thức có trong interface Array<T> từ những kinh nghiệm thực chiến xương máu nhé!

Nhóm 1: Thuộc Tính Cơ Bản & Định Dạng (Core & Indexing)

1. [n: number]: T (Index Signature)

Đây là cách TypeScript định nghĩa một mảng: Khi bạn truyền một chỉ số (index) kiểu number vào ngoặc vuông, bạn sẽ nhận về một phần tử có kiểu dữ liệu là T.

2. length: number — Chiếc Gương Đo Độ Dài

  • Ý nghĩa: Trả về số lượng phần tử hiện tại của mảng.
  • Kinh nghiệm thực chiến: Thuộc tính length trong JS không chỉ để đọc, nó có thể ghi được (writable). Nếu bạn chủ động gán arr.length = 0, bạn sẽ xóa sạch toàn bộ phần tử của mảng đó ngay lập tức. Nếu gán length nhỏ hơn số phần tử hiện tại, mảng sẽ bị cắt cụt.

3. toString() và 4. toLocaleString() — Chuỗi Hóa Mảng

  • toString(): Chuyển các phần tử trong mảng thành chuỗi bằng hàm toString của từng phần tử rồi nối chúng lại với nhau bằng dấu phẩy ,.
  • toLocaleString(): Tương tự như trên, nhưng nó sử dụng cơ chế định dạng theo vùng địa lý (Locale) của hệ thống (Ví dụ: định dạng ngày tháng hoặc dấu phân cách tiền tệ).

Nhóm 2: Thao Tác Thêm / Xóa Ở Hai Đầu (Stack & Queue)

Đây là nhóm hàm trực tiếp thay đổi (Mutate) mảng gốc.

5. push(...items: T[]): number — Thêm Vào Đuôi

  • Ý nghĩa: Thêm một hoặc nhiều phần tử vào cuối mảng.
  • Giá trị trả về: Số lượng phần tử mới của mảng sau khi thêm.

6. pop(): T | undefined — Rút Từ Đuôi

  • Ý nghĩa: Xóa phần tử cuối cùng và trả về chính phần tử đó. Nếu mảng rỗng, trả về undefined.

7. unshift(...items: T[]): number — Thêm Vào Đầu

  • Ý nghĩa: Nhét thêm các phần tử vào vị trí đầu tiên của mảng.
  • Giá trị trả về: Chiều dài mới của mảng.

8. shift(): T | undefined — Rút Từ Đầu

  • Ý nghĩa: Lấy phần tử đầu tiên ra khỏi mảng và đẩy các phần tử phía sau lên.

Cảnh báo hiệu năng (O(1)O(1) vs O(N)O(N)):

  • push và pop thao tác ở cuối mảng nên tốc độ cực nhanh (O(1)O(1)) vì các phần tử cũ không cần phải di chuyển vị trí trên RAM.
  • unshift và shift thao tác ở đầu mảng nên cực kỳ tốn tài nguyên (O(N)O(N)). Khi bạn shift() một mảng có 1 triệu dòng, toàn bộ 999.999 dòng còn lại phải dịch chuyển ô nhớ lên trước 1 bước. Hãy hạn chế dùng shift/unshift trên các mảng lớn!

Nhóm 3: Cắt, Ghép Và Biến Đổi Cấu Trúc (Slicing & Splitting)

9. concat(...items: (T | ConcatArray<T>)[]): T[] — Nối Mảng Thượng Đẳng

  • Ý nghĩa: Gộp hai hoặc nhiều mảng lại với nhau.
    
  • Đặc tính: Hàm này không thay đổi mảng cũ mà trả về một mảng mới tinh (Immutable).

10. join(separator?: string): string — Biến Mảng Thành Chuỗi

  • Ý nghĩa: Nối tất cả các phần tử thành một chuỗi duy nhất thông qua ký tự phân cách separator. Nếu bỏ trống, mặc định dùng dấu phẩy ,.
    

11. slice(start?: number, end?: number): T[] — Trích Xuất Dữ Liệu An Toàn

  • Ý nghĩa: Cắt một mảng con từ vị trí start đến trước vị trí end (không bao gồm end).
    
    Đặc tính: Trả về mảng mới, giữ nguyên mảng gốc. Nếu truyền số âm (ví dụ -2), nó sẽ tính ngược từ cuối mảng lên.

12. splice(start: number, deleteCount?: number, ...items: T[]): T[] — Con Dao Đa Năng

  • Ý nghĩa: Cắt, xóa, hoặc chèn thêm phần tử vào bất kỳ vị trí nào ở giữa mảng.
    

    Đặc tính: Hàm này phá hủy mảng gốc! Nó trả về một mảng chứa các phần tử đã bị xóa.

    Phân biệt Slice và Splice (Mẹo nhớ cho cuộc phỏng vấn):

  • Slice có chữ L -> Like (Yêu thương mảng gốc, chỉ copy sao chép).
    

    Splice có thêm chữ P -> Phá hủy (Thao tác trực tiếp, làm nát mảng gốc).

Nhóm 4: Sắp Xếp & Tìm Kiếm Vị Trí (Sorting & Searching)

13. reverse(): T[] — Đảo Ngược Thế Cờ

  • Đảo ngược thứ tự các phần tử trong mảng ngay tại chỗ (Mutate mảng gốc).
    

14. sort(compareFn?: (a: T, b: T) => number): this — Cạm Bẫy Sắp Xếp

  • Ý nghĩa: Sắp xếp mảng tại chỗ.
    
    Vết sẹo thực chiến: Nếu gọi arr.sort() mà không truyền hàm so sánh compareFn, JavaScript sẽ tự động biến các phần tử thành chuỗi rồi sắp xếp theo mã UTF-16. Hậu quả: [11, 2, 22, 1].sort() sẽ cho ra kết quả đi vào lòng đất: [1, 11, 2, 22] (Vì chuỗi "11" có ký tự đầu là "1" nên đứng trước "2"). Hãy luôn viết: .sort((a, b) => a - b) khi xếp số!

15. indexOf() và 16. lastIndexOf() — Tìm Vị Trí Chỉ Mục

indexOf: Tìm vị trí xuất hiện đầu tiên của phần tử từ trái sang phải. Nếu không thấy, trả về -1.
lastIndexOf: Tìm vị trí xuất hiện cuối cùng của phần tử (quét ngược từ phải sang trái).

Nhóm 5: Các Hàm Bất Đồng Bộ / Lập Trình Hàm (Functional Programming)

Đây là nhóm hàm nhận vào một hàm callback (predicate) để thực thi logic trên từng phần tử.

17. forEach(callbackfn: (value: T, index: number, array: T[]) => void): void

  • Duyệt qua từng phần tử của mảng. Hàm này luôn trả về void (không có giá trị). Bạn không thể dùng break hoặc continue để dừng vòng lặp forEach giữa chừng được.
    

18. map<U>(callbackfn: (value: T, index: number, array: T[]) => U): U[]

  • Duyệt qua các phần tử, biến đổi chúng thông qua hàm callback và trả về một mảng mới có độ dài tương đương nhưng chứa kiểu dữ liệu mới <U>.
    

19. filter(predicate: (value: T, index: number, array: T[]) => unknown): T[]

Lọc dữ liệu. Giữ lại các phần tử mà hàm callback trả về giá trị truthy.

20. some(predicate: ...): boolean và 21. every(predicate: ...): boolean

  • some: Chỉ cần ít nhất một phần tử thỏa mãn điều kiện là trả về true. (Dừng vòng lặp ngay khi thấy phần tử đúng).
    
    every: Bắt buộc tất cả phần tử phải thỏa mãn điều kiện thì mới trả về true.

Nhóm 6: Gom Tụ Dữ Liệu (The Reducers)

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;

22. reduce() và reduceRight() — Sức Mạnh Tối Thượng

Ý nghĩa: Tích lũy (Gom tụ) mảng dữ liệu thành một giá trị duy nhất (có thể là một số, một chuỗi, một object, hoặc thậm chí là một mảng khác).

reduceRight(): Hoạt động y hệt như reduce() nhưng duyệt từ cuối mảng lên đầu mảng.

Quy tắc sinh tồn với Reduce:

Hãy luôn luôn truyền giá trị khởi tạo initialValue. Nếu bạn không truyền, reduce sẽ lấy phần tử đầu tiên của mảng làm giá trị khởi tạo. Nếu gặp trường hợp mảng của bạn bị rỗng [], hệ thống sẽ ném lỗi runtime lập tức sập app: TypeError: Reduce of empty array with no initial value.

Bảng Tổng Kết Nhanh: Hàm Nào Sạch (Immutable) - Hàm Nào Bẩn (Mutate)? Để viết code an toàn, đặc biệt là khi làm việc với React (nơi quản lý State nghiêm ngặt), anh em cần ghi nhớ bảng phân loại này:

Hàm Giữ Nguyên Mảng Gốc (An Toàn) Hàm Thay Đổi Mảng Gốc (Nguy Hiểm)
concat, slice, join, toString push, pop, shift, unshift
map, filter, reduce splice, sort, reverse
Hiểu rõ bản chất định nghĩa kiểu của interface Array<T> giúp chúng ta không chỉ làm chủ công cụ mà còn tăng tư duy tối ưu hóa hiệu năng hệ thống. Lần tới khi chuẩn bị gõ một phương thức mảng, hãy khựng lại 1 giây xem nó có Mutate mảng gốc không nhé anh em!

Chúc anh em ứng dụng thành công! Happy Coding!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí