Iterator JavaScript là gì

Khái niệm Iterables và Symbol.iterator trong JavaScript

By Thedevmind | Feb 13, 2021
Iterator JavaScript là gì
Iterator JavaScript là gì

Bài viết trước: Các hàm xử lý mảng (array) trong JavaScript

Iterables hay tính lặp là concept cho phép bạn duyệt qua bất kỳ object nào với vòng lặp (for..of)

Tất nhiên mảng có tính lặp, một số kiểu hay objects trong JavaScript cũng là iterable, như chuỗi (strings) chẳng hạn.

Nếu bạn có một object không phải là array, tuy nhiên object này vẫn biểu diễn một loạt các items gì đấy và bạn muốn lặp (duyệt) qua toàn bộ phần tử, chúng ta có thể biến object này thành iterable và sử dụng for..of.

Symbol.iterator cho phép chúng ta định nghĩa một object có khả năng sử dụng vòng lặp foreach và biến nó thành Iterable object.

Symbol.iterator

Để nắm được concept về iterables, hãy thử tự tạo một object không phải array nhưng vẫn có thể dùng for..of:

let range = { from: 1, to: 5 }; // for(let num of range) ... num=1,2,3,4,5

Chúng ta muốn lặp các số từ object.from đến object.to, để biến range thành iterable thì bạn cần phải thêm thuộc tính [Symbol.iterator] cho nó.

Chúng ta sẽ xem qua ví dụ và mình sẽ giải thích cách hoạt động sau:

let range = { from: 1, to: 5 }; range[Symbol.iterator] = function() { return { current: this.from, last: this.to, if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; for (let num of range) { alert(num); // 1, đến 2, 3, 4, 5

Iterator

Object mà phương thức của [Symbol.iterator] trả về được gọi là iterator:

  • current: giá trị hiện tại.
  • last: giá trị cuối cùng.
  • next(): phương thức được for..of gọi để trả về giá trị tiếp theo hoặc dừng vòng lặp.

Trong đó, iterator chỉ cần phương thức next() là đủ, 2 giá trị current và last chỉ để phục vụ trong hàm next() giúp nó biết khi nào dừng lại và trả về giá trị nào.

Cách hoạt động của iterator và for..of

Chúng ta sẽ đi từng bước của for..of khi hoạt động với iterable object:

  1. Khi for..of khởi tạo, nó sẽ gọi tới phương thức Symbol.iterator của object và lấy được iterator object.
  2. Tiếp theo, for..of chỉ hoạt động với iterator object, không cần tới hàm Symbol.interator nữa.
  3. Khi for..of muốn tìm giá trị tiếp theo, nó sẽ gọi next() của interator.
  4. Kết quả của next() có dạng {done: true/ false, value: any }, nếu done bằng true thì dừng vòng lặp, ngược lại sử dụng value là giá trị tiếp theo.

Bạn có thể xem lại ví dụ để áp dụng giải thích trên vào ví dụ nhé.

Nếu đã hiểu cách hoạt động, để ngắn gọn hơn thì bạn có thể biến luôn object range thành iterator:

let range = { from: 1, to: 5, [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; for (let num of range) { alert(num); // 1, then 2, 3, 4, 5 }

Chỉ cần thêm phương thức next() vào object range và xử lý trả về giá trị mà for..of cần.

Nhưng cách làm này có một lỗ hổng, bạn không thể có 2 vòng for..of độc lập với object này bởi chúng sẽ sử dụng chung giá trị range.current.

Một số lưu ý nhỏ

Lặp vô hạn

Định nghĩa hàm next không đúng sẽ làm chương trình lặp vô hạn, giả thiết như giá trị range.to = Infinity hoặc next không bao giờ trả về { done: true }.

Tất nhiên bạn vẫn có thử sử dụng break để phá vòng lặp nếu next() bị định nghĩa sai.

Giá trị trả về của next()

Đừng hiểu nhầm rằng object mà next trả về { done: true/false, value: any }, luôn có thuộc tính value là số nhé.

Giá trị value có thể là bất kỳ kiểu nào, có thể là value: { id, user } hay value: "abc",

Chuỗi là iterable

Ok, chúng ta đã biết rằng arrays strings là 2 loại iterables được JavaScript hỗ trợ sẵn (hoặc gọi là built-in iterables).

Sử dụng for..of với string để lặp qua toàn bộ ký tự:

for (let char of "Hello") { alert( char ); // H, đến e, l, l, o }

Và nó hoạt động được với cả cặp 2-byte (surrogate pairs)

let str = '𝒳'; for (let char of str) { alert( char ); // 𝒳, đến }

Surrogate pairs là khái niệm ký tự nâng cao, bạn có thể đọc thêm ở bài Các hàm xử lý chuỗi trong JavaScript

Gọi một iterator tường minh

Đây cũng là cách mà foreach trong C# hay Coroutine trong Unity hoạt động khi code được dịch:

let str = "Hello"; //------ viết tường minh --------- let iterator = str[Symbol.iterator](); while (true) { let result = iterator.next(); if (result.done) break; alert(result.value); // outputs characters one by one } // ------ viết ngầm định ---------- for (let char of str) alert(char);

Đây cũng có thể xem là cách mà trình biên dịch thực hiện behind the scene, càng hiểu bản chất ở low-level, bạn càng dễ dàng kiểm soát vòng lặp hơn.

Phân biệt iterables và object giống mảng (array-like object)

Để không bị rối, hãy tóm lược định nghĩa lại một chút:

  • Iterables: là các objects có định nghĩa phương thức Symbol.iterator.
  • Objects giống mảng: là các objects lưu trữ nhiều phần tử theo số và có thuộc tính length, vì vậy nó giống mảng.

Ví dụ:

  • Strings là một ví dụ về object giống mảng đồng thời là iterable.
  • Object range ở ví dụ trước là iterable nhưng không phải là object giống mảng.
let arrayLike = { // object giống mảng 0: "Hello", 1: "World", length: 2 }; // Lỗi, bởi arrayLike không phải là iterables for (let item of arrayLike) {}

Nếu thêm phương thức Symbol.iterator cho arrayLike, bạn cũng chỉ có thể duyệt mà không sử dụng các phương thức push, pop, như một mảng chân chính.

Để làm được điều này, chúng ta qua phần tiếp theo.

Array.from

Phương thức thần kỳ Array.from biến một iterable hay object giống mảng thành một mảng, cú pháp:

Array.from(obj[, mapFn, thisArg])

Ví dụ biến object giống mảng thành mảng:

let arrayLike = { 0: "Hello", 1: "World", length: 2 }; let arr = Array.from(arrayLike); alert(arr.pop()); // World, như mảng

Hàm này sẽ tạo một array hoàn toàn mới và sao chép toàn bộ phần tử của tham số, xem thử với iterable (sử dụng object range ở ví dụ trước):

let arr = Array.from(range); // biến interator thành array alert(arr); // 1,2,3,4,5

Tham số thứ 2 có tác dụng biến đổi phần tử, tương tự .map của array vậy:

let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25

Biến string thành array?

String là object giống mảng đồng thời vừa là iterable object, vì vậy bạn hoàn toàn có thể sử dụng Array.from(str)

Kết quả trả về là một mảng chứa các ký tự:

let str = 'Hi'; let chars = Array.from(str); // biến chuỗi thành mảng alert(chars[0]); // H alert(chars[1]); // i alert(chars.length); // 2

Bạn có thể viết đoạn code trên tường minh để hiểu cách mà nó hoạt động:

let str = 'Hi'; let chars = []; for (let char of str) { chars.push(char); } alert(chars);

Cuối bài viết

Chúng ta đã biết về Iterables cũng như Iterator là gì, 2 khái niệm tồn tại trong rất nhiều ngôn ngữ lập trình khác sử dụng vòng lặp (foreach,) và rất dễ bị nhầm lẫn.

Bài tiếp theo: Kiểu Map và Set trong JavaScript

Nếu các bạn thấy bài viết này hữu ích hoặc có vấn đề thắc mắc về chủ đề này hay cần thêm thông tin nào trong bài, vui lòngcommentbên dưới bài cho mình biết nhé.