Menu

Mảng trong JavaScript: tạo, truy cập, sửa đổi và duyệt mảng

Cách mảng hoạt động trong JavaScript: tạo mảng, chỉ số, length, push/pop, slice và splice, và những kiểu vòng lặp dễ đọc nhất.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Mảng là một danh sách có thứ tự

Mảng (array) trong JavaScript chứa một dãy giá trị có thứ tự. Các giá trị có thể thuộc bất kỳ kiểu nào, như số, chuỗi, đối tượng, thậm chí là mảng khác, và không nhất thiết phải cùng kiểu. Bạn tạo mảng bằng dấu ngoặc vuông:

Cú pháp literal đó là cách bạn sẽ tạo mảng trong 99% trường hợp. Ngoài ra còn có new Array(...), nhưng nó có một điểm dễ vấp: new Array(3) tạo ra một mảng có độ dài 3 không chứa phần tử thật nào, chứ không phải [3]. Cứ dùng [] là được.

Chỉ số bắt đầu từ 0

Bạn truy cập phần tử theo vị trí, bắt đầu từ 0:

Có hai điều cần lưu ý. Thứ nhất, truy cập vượt quá cuối mảng không gây lỗi mà trả về undefined. Đây là nguồn gốc quen thuộc của những bug kiểu "sao cái này lại undefined?". Thứ hai, bạn có thể gán giá trị vào bất kỳ chỉ số nào, kể cả chỉ số vượt quá cuối mảng, và JavaScript sẽ vui vẻ nới rộng mảng:

Vị trí 3 và 4 trở thành "ô trống" (lỗ hổng trong mảng thưa). Độ dài nhảy lên 6. Hiếm khi bạn muốn điều này: đó là tác dụng phụ của việc mảng quá linh hoạt, không phải tính năng để dựa vào.

length luôn được cập nhật

array.length luôn lớn hơn chỉ số cao nhất đúng một đơn vị. Nó tự động cập nhật khi bạn thêm hoặc xóa phần tử:

Mẹo cuối cùng đáng ghi nhớ: gán giá trị cho length sẽ cắt ngắn mảng. Đặt nums.length = 0 là cách nhanh để làm rỗng mảng ngay tại chỗ.

push, pop, shift, unshift

Bốn phương thức để thêm và xóa ở hai đầu mảng:

  • push(x) thêm vào cuối, trả về độ dài mới.
  • pop() xóa ở cuối, trả về phần tử vừa xóa.
  • unshift(x) thêm vào đầu.
  • shift() xóa ở đầu.

push/pop chạy nhanh. shift/unshift phải đánh lại chỉ số cho mọi phần tử còn lại, nên chậm hơn với mảng lớn: không sao với kích thước thường ngày, nhưng đáng biết nếu bạn làm việc với hàng triệu phần tử.

slice và splice

Hai phương thức này có tên na ná nhau nhưng hoạt động hoàn toàn khác nhau. Phân biệt rõ chúng, bạn sẽ đỡ được rất nhiều lần vò đầu bứt tai.

slice(start, end) trả về một bản sao của một phần mảng. Mảng gốc không bị đụng đến. end không được tính vào:

splice(start, deleteCount, ...items) sửa đổi mảng. Nó có thể xóa, chèn, hoặc cả hai, và trả về những gì nó đã xóa:

Mẹo nhớ: nếu từ đó khiến bạn nghĩ đến việc cắt một lát bánh mì ra (lấy bản sao), đó là slice. Nếu nó khiến bạn nghĩ đến việc nối phim (cắt rồi chèn), đó là splice.

Tìm kiếm: indexOf, includes, find

Ba cách để tra cứu, cho các tình huống khác nhau:

  • indexOf(x) cho bạn vị trí, hoặc -1 nếu không tìm thấy. Hữu ích khi bạn cần chỉ số.
  • includes(x) cho bạn một giá trị boolean. Gọn hơn khi bạn chỉ muốn biết "giá trị này có trong mảng không?". Nó còn xử lý đúng NaN, điều mà indexOf không làm được.
  • find(predicate) chạy một hàm cho từng phần tử và trả về phần tử đầu tiên khớp. Đây là lựa chọn cho mảng chứa đối tượng, nơi indexOf và includes chỉ so sánh bằng nghiêm ngặt.

Duyệt qua mảng

Có vài cách tốt, mỗi cách hợp với một tình huống:

for...of là lựa chọn mặc định: dễ đọc, dùng được với mọi iterable, cho bạn giá trị trực tiếp. forEach ổn khi bạn cần cả chỉ số và không thoát vòng lặp sớm (bạn không thể dùng break để thoát khỏi forEach). Vòng lặp for cổ điển với chỉ số thì dài dòng nhưng cho bạn toàn quyền kiểm soát: dùng nó khi bạn cần bỏ qua phần tử, duyệt ngược, hoặc cả hai.

Một kiểu vòng lặp nên tránh: for...in. Nó duyệt qua các khóa và tính cả thuộc tính được kế thừa. Nó dành cho đối tượng, không phải mảng.

Sao chép và kết hợp mảng

Mảng là giá trị tham chiếu. Gán một mảng cho biến khác không tạo bản sao, mà chỉ tạo thêm một cái tên cho cùng một mảng:

Để thật sự sao chép, hãy dùng toán tử spread hoặc slice():

Cả hai đều cho bạn một bản sao nông: mảng ngoài cùng là mới, nhưng nếu các phần tử là đối tượng thì những đối tượng đó vẫn được dùng chung. Spread cũng là cách gọn nhất để ghép các mảng:

Mảng chứa đối tượng

Trong code thực tế, phần lớn mảng chứa đối tượng. Các phương thức vẫn như cũ, nhưng việc so khớp trở nên thú vị hơn:

filter trả về một mảng mới gồm các phần tử khớp với vị từ. map trả về một mảng mới trong đó mỗi phần tử đã được biến đổi. Nối chuỗi hai phương thức này là việc cơm bữa khi làm việc với mảng đối tượng, và trang tài liệu tiếp theo sẽ đi sâu hơn vào nhóm phương thức đó.

Mảng và đối tượng

Một mô hình tư duy nhanh: dùng mảng khi thứ tự quan trọng và bạn đang làm việc với một danh sách các thứ cùng loại. Dùng đối tượng (hoặc Map) khi bạn đặt khóa cho các thứ theo tên. "Thứ nhất, thứ hai, thứ ba" là địa bàn của mảng. "Tìm người dùng có id 42" là địa bàn của đối tượng hoặc Map.

Bạn có thể kết hợp chúng, mảng chứa đối tượng có mặt ở khắp nơi, nhưng đừng dùng mảng như một từ điển có khóa, và đừng dùng đối tượng như một danh sách có thứ tự. Mỗi thứ làm tốt một việc.

Tiếp theo: destructuring

Việc lấy giá trị ra khỏi mảng theo chỉ số (và lấy thuộc tính ra khỏi đối tượng theo tên) phổ biến đến mức JavaScript có hẳn một cú pháp riêng cho nó. Đó là destructuring, nội dung của trang tiếp theo.

Câu hỏi thường gặp

Làm sao để tạo mảng trong JavaScript?

Dùng cú pháp literal với dấu ngoặc vuông: const fruits = ['apple', 'banana']. Đó là cách viết chuẩn. Hàm khởi tạo Array (new Array(3)) vẫn tồn tại nhưng hiếm khi là thứ bạn cần: new Array(3) tạo ra một mảng có độ dài 3 mà không có phần tử thật nào, điều này khiến nhiều người bất ngờ.

Làm sao để xóa một phần tử khỏi mảng trong JavaScript?

Xóa ở cuối thì dùng pop(). Xóa ở đầu thì dùng shift(). Xóa ở bất kỳ vị trí nào ở giữa thì dùng splice(index, 1) để sửa trực tiếp, hoặc filter(...) để nhận một mảng mới không chứa phần tử đó. splice thay đổi mảng gốc; filter thì không.

slice và splice khác nhau thế nào?

slice(start, end) trả về một bản sao nông của một phần mảng và để nguyên mảng gốc. splice(start, deleteCount, ...items) sửa đổi mảng gốc: nó xóa phần tử, có thể chèn phần tử mới và trả về các phần tử đã xóa. Chỉ khác một chữ cái mà cách hoạt động rất khác nhau.

Làm sao để kiểm tra một giá trị có nằm trong mảng JavaScript không?

Dùng array.includes(value): nó trả về true hoặc false và xử lý đúng cả NaN. indexOf(value) trả về vị trí, hoặc -1 nếu không tìm thấy, hữu ích khi bạn cũng cần chỉ số. Với các phép so khớp phức tạp, hãy dùng some(predicate) hoặc find(predicate).

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu