Hi 🤓 Cảm ơn bạn đã ghé thăm blog này, nếu những bài viết trên blog giúp ích cho bạn. Bạn có thể giúp blog hiển thị quảng cáo bằng cách tạm ngừng ad blocker 😫 và để giúp blog duy trì hoạt động nếu bạn muốn.
Cảm ơn bạn!

Bạn đã bao giờ gặp lỗi tương tự như dưới đây trong khi xử lý mảng JavaScript chưa?

Uncaught TypeError: data.forEach is not a function

forEach là một method dùng để xử lý mảng trong JavaScript, xảy ra lỗi ở trên có thể là do:

  1. Chúng ta có thể đang không sử dụng forEach với Mảng, ví dụ như sử dụng forEach với một object hoặc một chuỗi,...
  2. Sử dụng forEach trên một Array-like object mà chúng ta nhầm tưởng là một mảng nhưng không phải vậy.

Trong bài viết này, chúng ta sẽ làm rõ Array-like object trong JavaScript là gì và cách sử dụng nó nhé!

Array-like object trong JavaScript là gì?

Trong JavaScript, object được sử dụng để lưu trữ nhiều giá trị. Chúng ta có thể tạo một object bằng dấu ngoặc nhọn { } với những thuộc tính (property) bên trong nó.

Mảng trong JavaScript là một tập hợp có thứ tự và có thể chứa giá trị với kiểu dữ liệu khác nhau. Mảng có thể được tạo bằng dấu ngoặc vuông [ ], các phần tử sẽ nằm trong nó.

Quay trở lại với Array-like, đây chính xác là một object, tuy nhiên nó có những điểm đặc biệt sau:

  1. Có thuộc tính length của một số nguyên không âm và thuộc tính được lập chỉ mục index (với key là các số). Thuộc tính được lập chỉ mục tức là chúng ta có thể truy xuất các giá trị giống như array, ví dụ array_like[0].
  2. Không có bất kỳ các method của Array như: push, pop, join, map, ...

Dưới đây là một ví dụ về Array-like object:

const arr_like = {0: 'JavaScript', 1: 'ReactJS', 2: 'GatsbyJS', length: 3};

Với Array-like object chúng ta có thể truy xuất các giá trị như sau:

arr_like[1]; // 'ReactJS'
arr_like.length; // 3

Các bạn thấy cách truy xuất ở trên nhìn rất giống với truy xuất mảng mà chúng ta thường sử dụng đúng không 😁.

Nếu tìm hiểu kỹ thì chúng ta sẽ biết array-like hoàn toàn khác với một mảng bình thường, nó không được tạo bởi Array() constructor hay sử dụng ký tự []. Vì lý do đó Array-like sẽ không được kế thừa bất cứ thứ gì từ Array.prototype nên chúng ta sẽ không thấy các method của array khi sử dụng Array-like.

Ngoài ra, thuộc tính length của array-like sẽ không tự động update như thuộc tính length của mảng. Chúng ta sẽ không thể loại bỏ bớt phần tử bằng cách giảm length như khi sử dụng mảng.

Để rõ ràng hơn thì các bạn có thể kiểm tra nó sử dụng Array.isArray():

Array.isArray(arr_like); // false

Một số array-like object thường gặp

Chúng ta thường gặp Array-like khi làm việc với function cụ thể là arguments và khi sử dụng method như getElementsByTagName().

arguments là một array-like object có thể truy cập bên trong các function, nó chứa các giá trị của các đối số được truyền cho hàm đó.

function sum() {
   console.log(arguments);
}

Giả sử ta truyền vào đó 2 giá trị:

sum(10, 20);

Cùng xem kết quả ở console:

arguments is an Array-like object

Như bạn thấy ở trên [[Prototype]] của nó là Object chứ không phải là Array. arguments là một array-like nên nó có các thuộc tính với key là các số và thuộc tính length.

🤚 Tìm hiểu về prototype các bạn đọc tại: Tìm hiểu về Prototype trong JavaScript.

function sum() {
   console.log(arguments.length);
}

sum(10, 20, 30); // length là 3

Nhìn hình trên, một số bạn có thế nghĩ rằng arguments là một array vì nó có length và các chữ số index. Giả sử chúng ta gọi method push() để thêm một phần tử vào arguments:

function sum() {
   console.log(arguments.push(40));
}

sum(10, 20, 30);

Chúng ta sẽ nhận được lỗi, lỗi này cũng tương tự khi ta sử dụng các method của Array.

Uncaught TypeError: arguments.push is not a function

Khi làm việc với DOM, chắc hẳn các bạn đã sử dụng một số method như: getElementsByTagName(), querySelectorAll(),...

Hai method mình kể trên đều là những array-like object. Chúng ta cùng xem một ví dụ.

Giả sử mình có đoạn html như này:

<div id="Homiedev-posts">
  <ul>
    <li>
      <a href="https://homiedev.com/hoc-javascript-co-ban/">
        Học JavaScript cơ bản cùng homiedev
      </a>
    </li>
    <li>
      <a href="https://homiedev.com/javascript-projects-for-beginners/">
        100+ Project JavaScript
      </a>
    </li>
  </ul> 
</div>

Bây giờ ta thử sử dụng querySelectorAll() để lấy tất cả thẻ a ở trên:

const tags = document.querySelectorAll('a');

console.log(tags);

Kết quả nhận được:

querySelectorAll return a NodeList - Array-like object

Như bạn thấy kết quả trả về là một NodeList chứa phần tử là thẻ a. Nhìn sơ qua thì cứ nghĩ đây là một array nhưng thực chất thì không phải. Đây là một array-like object, tuy nhiên [[Prototype]] của nó có các method như entries(), forEach(),... Chúng ta có thể lặp qua các phần tử này bằng cách sử dụng forEach() ^^ các bạn có thể tự mình test thử nhé 😁.

Khi sử dụng getElementsByTagName() kết quả chúng ta nhận được là một HTMLCollection thay vì NodeList như querySelectorAll().

const tags = document.getElementsByTagName("a");

console.log(tags);

getElementsByTagName return a HTMLCollection - Array-like object

Như bạn thấy ở trên, HTMLCollection cũng là một array-like object 😁.

Chuyển array-like object thành mảng

Trong một số trường hợp, chúng ta muốn chuyển array-like object thành array, mình xin đưa ra một số cách để làm điều đó.

Sử dụng ES6 Spread operator

Chúng ta có thể sử dụng ES6 Spread operator ([...array-like]) để chuyển đổi array-like thành một mảng.

Ví dụ:

function sum() {
   console.log([...arguments]);
}

sum(10, 20);

Kết quả:

[10, 20]

Các bạn lưu ý: Spread operator hoạt động với các object là iterable, array, string. arguments bản thân nó là một iterable object nên chúng ta sử dụng [...arguments] sẽ không báo lỗi. Trường hợp chúng ta tự tạo ra một array-like và sử dụng spread operator có thể dẫn đến lỗi, lý do là vì array-like object này không là iterable. Cùng xem ví dụ dưới đây:

const arr_like = { 0: "a", 1: "b", length: 2 };
console.log([...arr_like]);
// lỗi: Uncaught TypeError: arr_like is not iterable

Bạn có thể tìm hiểu thêm về iterable tại: Iterators và Iterables trong JavaScript bạn đã biết chưa?.

Sử dụng Array.from()

Chúng ta có thể sử dụng Array.from(array-like) để chuyển array-like thành mảng.

Ví dụ chúng ta chuyển một HTMLCollection thành mảng như sau:

const tags = document.getElementsByTagName("a");

console.log(Array.from(tags));

convert an Array-like to an Array

Kết quả các bạn sẽ thấy [[Prototype]] của nó lúc này sẽ là Array.

Sử dụng slice() method

Để chuyển array-like thành mảng chúng ta có thể sử dụng slice() như sau:

const args = Array.prototype.slice.call(arguments);

Ở cách làm trên, slice() sẽ đọc thuộc tính length của this, lúc này là arguments. Sau đó, nó đọc các thuộc tính có key là một số nguyên từ đầu đến cuối và đưa các giá trị của chúng vào một mảng mới.


Bài viết đã khá dài rồi 😁, mình xin tóm tắt lại một số ý chính về array-like như sau:

  1. Array-like không phải là mảng. Nó chỉ giống mảng ở điểm có thể truy cập giá trị bằng chỉ số index và thuộc tính length.
  2. Array-like cũng giống như một object JavaScript bình thường. Chúng ta có thể sẽ gặp nhiều array-like object trong quá trình sử dụng JavaScript.
  3. Một số cách để chuyển đổi array-like object thành mảng: spread operator, method Array.from(), method slice().
Có thể bạn thích ⚡
homiedev
About Me

Hi, I'm @devnav. Một người thích chia sẻ kiến thức, đặc biệt là về Frontend 🚀. Trang web này được tạo ra nhằm giúp các bạn học Frontend hiệu quả hơn 🎉😄.

Chúc các bạn tìm được kiến thức hữu ích trong blog này 😁😁.