Cảm ơn bạn!
Iterators và Iterables trong JavaScript bạn đã biết chưa?
Chào các bạn, tiếp tục với những bài viết về học JavaScript cơ bản, hôm nay mình xin giới thiệu đến các bạn khái niệm Iterators và Iterables trong JavaScript nhé!
Trong JavaScript, để duyệt qua một đối tượng trong mảng thì chúng ta có thể sử dụng các câu lệnh lặp như for, for...in, for...of, while.
Một điều khá hay là nếu sử dụng for...of
thì chúng ta không chỉ lặp qua mảng mà còn có thể lặp qua một object cụ thể là iterable object.
Vậy để hiểu thế nào là một iterable object thì chúng ta cần tìm hiểu về khái niệm Iteration protocols trước nhé.
Iteration protocols trong JavaScript
Iteration protocols (giao thức lặp) là tập hợp các quy tắc mà một object cần tuân theo. Khi đó, ta có thể sử dụng một vòng lặp để lặp qua các giá trị của đối tượng theo ý muốn.
JavaScript có hai giao thức lặp là iterator protocol và iterable protocol.
Iterator protocol
Các bạn có thể hiểu Iterator là một object triển khai giao thức Iterator protocol. Để triển khai giao thức này thì một object cần có method next()
return về một object khác với 2 thuộc tính (properties):
done
: một giá trị boolean cho biết còn hay không còn phần tử nào có thể được lặp lại.value
: giá trị hiện tại.
Mỗi lần chúng ta gọi next()
, nó sẽ trả về giá trị tiếp theo:
{ value: 'next value', done: false }
Nếu chúng ta gọi next()
sau khi giá trị cuối cùng trả về, thì kết quả trả về sẽ như sau:
{ done: true: value: undefined }
Giá trị true của done
cho biết không còn giá trị nào để trả về và giá trị của value
lúc này sẽ là undefined.
Để hiểu rõ hơn thì chúng ta sẽ xem ví dụ ở phần sau nhé 😁. Tiếp theo chúng ta sẽ tìm hiểu về một protocol khác là Iterable protocol.
Iterable protocol
Một object là Iterable (có thể lặp lại) khi nó chứa một method được gọi là [Symbol.iterator]
không nhận đối số và trả về một object tuân theo giao thức Iterator protocol.
Iterators trong JavaScript
JavaScript ES6 cung cấp các built-in iterators (trình lặp tích hợp sẵn) cho Array, Set, và Map, vì vậy chúng ta không phải tạo iterators cho các đối tượng này.
Nếu muốn tùy chỉnh các giá trị có thể lặp lại của object, chúng ta cần sử dụng iteration protocols kết hợp với for ... of
để lặp qua các giá trị này.
Sau đây là một ví dụ các bạn có thể xem qua:
Giả sử mình có một object như bên dưới. Sau đó sử dụng for ... of
để lặp qua các giá trị.
const obj = {
blog: "homiedev.com",
categories: ["ReactJS", "JavaScript", "TypeScript"],
isFrontEndBlog: true,
};
for (const element of obj) {
console.log(element);
}
// lỗi: Uncaught TypeError: obj is not iterable
Ở đoạn code trên, chúng ta sẽ gặp lỗi Uncaught TypeError: obj is not iterable. Lý do là vì chúng ta đang sử dụng một object thông thường (plain objects), hay object này không có khả năng lặp lại.
Như mình đã nói ở trên, để object là iterable chúng ta cần triển khai Iterable protocol. Đây là một cách triển khai các bạn có thể tham khảo:
const obj = {
blog: "homiedev.com",
categories: ["ReactJS", "JavaScript", "TypeScript"],
isFrontEndBlog: true,
};
obj[Symbol.iterator] = function () {
let i = 0;
let values = Object.values(this);
return {
next: () => {
return {
value: values[i++],
done: i > values.length,
};
},
};
};
Trong đoạn code trên giá trị this
sẽ là obj
, để tìm hiểu về this
trong JavaScript các bạn có thể đọc bài viết: Tất tần tật về this trong JavaScript.
Khi object là Iterable chúng ta có thể sử dụng được for ... of
:
const obj = {
blog: "homiedev.com",
categories: ["ReactJS", "JavaScript", "TypeScript"],
isFrontEndBlog: true,
};
obj[Symbol.iterator] = function () {
let i = 0;
let values = Object.values(this);
return {
next: () => {
return {
value: values[i++],
done: i > values.length,
};
},
};
};
for (const element of obj) {
console.log(element);
}
Kết quả chúng ta được:
homiedev.com
['ReactJS', 'JavaScript', 'TypeScript']
true
Vì luồng vòng lặp ở trên là do chúng ta tự xử lý nên có thể dẫn đến trường hợp xảy ra vòng lặp vô hạn các bạn nên chú ý nhé ^^.
Ngoài ra, chúng ta có thể truy cập method [Symbol.iterator]()
như dưới đây:
console.log(typeof obj[Symbol.iterator]); // function
let iterator = obj[Symbol.iterator]();
let result = iterator.next();
while( !result.done ) {
console.log(result.value);
result = iterator.next();
}
Cleaning up
Ngoài method next()
, [Symbol.iterator]()
có thể trả về một method được gọi là return()
.
Method return()
được gọi tự động khi quá trình lặp bị dừng sớm. Chúng ta có thể viết code xử lý cleanup ở đây.
const obj = {
blog: "homiedev.com",
categories: ["ReactJS", "JavaScript", "TypeScript"],
isFrontEndBlog: true,
};
obj[Symbol.iterator] = function () {
let i = 0;
let values = Object.values(this);
return {
next: () => {
return {
value: values[i++],
done: i > values.length,
};
},
return: () => {
console.log("Dừng gọi next()...");
return {
done: true,
value: undefined,
};
},
};
};
for (const element of obj) {
if (Array.isArray(element)) break;
console.log(element);
}
Kết quả chúng ta được như sau:
homiedev.com
Dừng gọi next()...
Kiểm tra object là iterable
Sau khi đã hiểu về iterable là gì, chúng ta có thể kiểm tra một object có phải là iterable hay không bằng những cách sau ^^.
// Cách 1
function isIterable(obj) {
// kiểm tra falsy values
if (!obj) return false;
return typeof obj[Symbol.iterator] === 'function';
}
const obj1 = {symbol: '😴'};
const obj2 = {
count: 5,
next() {
const data = { value: "homiedev", done: false };
if (this.count === 0) data.done = true;
else this.count--;
return data;
},
[Symbol.iterator]() {
return this;
},
};
console.log(isIterable(obj1)); // false
console.log(isIterable(obj2)); // true
// Cách 2: sử dụng in operator
// Nếu value là null/undefined, Object sẽ return về empty object {}.
if (
Symbol.iterator in Object(obj1) ||
Symbol.iterator in Object(obj2)
) {
console.log('iterable 🎉');
}
Spread syntax
Một điều hay ho nữa mình muốn giới thiệu là chúng ta có thể sử dụng Spread syntax ...
cho một iterable chẳng hạn như string, array hay iterable object. Nhờ nó ta có thể chuyển iterable object thành một mảng như sau:
const obj = {
blog: "homiedev.com",
categories: ["ReactJS", "JavaScript", "TypeScript"],
isFrontEndBlog: true,
};
obj[Symbol.iterator] = function () {
let i = 0;
let values = Object.values(this);
return {
next: () => {
return {
value: values[i++],
done: i > values.length,
};
},
return: () => {
console.log("Dừng gọi next()...");
return {
done: true,
value: undefined,
};
},
};
};
console.log([...obj]);
Kết quả chúng ta được như sau:
['homiedev.com', Array(3), true]
Giả sử chúng ta sử dụng một plain object thì sẽ xảy ra một lỗi:
const obj = { key1: 'value1' };
const array = [...obj];
// Uncaught TypeError: obj is not iterable
Một số kiến thức liên quan trong bài: