Cảm ơn bạn!
Array-like Object trong JavaScript là gì? Tại sao bạn cần nên biết
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:
- 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,... - 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:
- 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]
. - 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:
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:
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);
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));
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:
- 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.
- 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.
- Một số cách để chuyển đổi array-like object thành mảng: spread operator, method
Array.from()
, methodslice()
.