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!

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 protocoliterable 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):

  1. 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.
  2. 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:

  1. Check empty array javascript.
  2. Check key exist in object javascript
  3. Khác nhau giữa for...in và for...of JavaScript
  4. Spread Operator Javascript là gì? cách sử dụng Spread Operator
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 😁😁.