1. Iterator Pattern là gì?
Iterator Pattern là một mẫu thiết kế hành vi cho phép duyệt qua các phần tử của một tập hợp tuần tự mà không cần phải biết chi tiết về cấu trúc bên trong của tập hợp đó. Pattern này tách biệt logic duyệt khỏi tập hợp, cho phép nhiều cách duyệt khác nhau trên cùng một tập hợp.
Các thành phần chính trong Iterator Pattern:
- Iterator: Interface định nghĩa các phương thức để duyệt qua tập hợp
- ConcreteIterator: Triển khai Iterator interface
- Aggregate: Interface định nghĩa phương thức tạo Iterator
- ConcreteAggregate: Triển khai Aggregate interface và trả về Iterator cụ thể
Structure
Iterator Pattern có cấu trúc như sau:
classDiagram
class Iterator {
<<interface>>
+hasNext()
+next()
+current()
+reset()
}
class ConcreteIterator {
-collection
-position
+hasNext()
+next()
+current()
+reset()
}
class Aggregate {
<<interface>>
+createIterator()
}
class ConcreteAggregate {
-items
+createIterator()
}
class Client
Aggregate <|.. ConcreteAggregate
Iterator <|.. ConcreteIterator
ConcreteAggregate ..> ConcreteIterator : creates
Client --> Aggregate
Client --> Iterator
note for Iterator "Interface định nghĩa các<br>phương thức duyệt"
note for ConcreteIterator "Triển khai các phương thức<br>duyệt cho tập hợp cụ thể"
note for Aggregate "Interface định nghĩa<br>phương thức tạo iterator"
note for ConcreteAggregate "Tạo iterator<br>cho tập hợp cụ thể"
Các đặc điểm chính của Iterator Pattern:
- Tách biệt trách nhiệm: Tách logic duyệt khỏi cấu trúc dữ liệu
- Đa dạng cách duyệt: Cho phép nhiều cách duyệt khác nhau trên cùng một tập hợp
- Ẩn chi tiết triển khai: Người dùng không cần biết cấu trúc bên trong của tập hợp
- Đơn giản hóa giao diện: Cung cấp giao diện đơn giản để duyệt qua các phần tử
2. Triển khai trong JavaScript
2.1 Ví dụ về Collection Iterator
Lưu ý đặt tên: tránh đặt tên class là
Iterator— từ ES2025 (Stage 4 Iterator Helpers),Iteratorlà constructor toàn cục (cùng vớiIterator.from,.map,.filter,.take…), sẽ xung đột nếu bạn shadow nó. Dùng tên rõ ràng nhưCollectionIteratorhoặcCursor.
// Iterator interface (tránh tên "Iterator" để không che global)
class CollectionIterator {
hasNext() {
throw new Error("hasNext() must be implemented");
}
next() {
throw new Error("next() must be implemented");
}
current() {
throw new Error("current() must be implemented");
}
reset() {
throw new Error("reset() must be implemented");
}
}
// Concrete Iterator
class ArrayIterator extends CollectionIterator {
constructor(collection) {
super();
this.collection = collection;
this.position = 0;
}
hasNext() {
return this.position < this.collection.length;
}
next() {
const item = this.collection[this.position];
this.position++;
return item;
}
current() {
return this.collection[this.position];
}
reset() {
this.position = 0;
}
}
// Aggregate interface
class Collection {
getIterator() {
throw new Error("getIterator() must be implemented");
}
}
// Concrete Aggregate
class NumberCollection extends Collection {
constructor() {
super();
this.items = [];
}
addItem(item) {
this.items.push(item);
}
getItems() {
return this.items;
}
getIterator() {
return new ArrayIterator(this.items);
}
}
// Usage
const numbers = new NumberCollection();
numbers.addItem(1);
numbers.addItem(2);
numbers.addItem(3);
numbers.addItem(4);
numbers.addItem(5);
const iterator = numbers.getIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
// Reset and iterate again
iterator.reset();
console.log("Iterating again...");
while (iterator.hasNext()) {
console.log(iterator.next());
}
2.2 Ví dụ về Tree Iterator
// Tree Node
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
// Tree Iterator Interface
class TreeIterator {
hasNext() {
throw new Error("hasNext() must be implemented");
}
next() {
throw new Error("next() must be implemented");
}
}
// In-order Iterator
class InOrderIterator extends TreeIterator {
constructor(root) {
super();
this.stack = [];
this.current = root;
}
hasNext() {
return this.current || this.stack.length > 0;
}
next() {
while (this.current) {
this.stack.push(this.current);
this.current = this.current.left;
}
const node = this.stack.pop();
this.current = node.right;
return node.value;
}
}
// Pre-order Iterator
class PreOrderIterator extends TreeIterator {
constructor(root) {
super();
this.stack = [root];
}
hasNext() {
return this.stack.length > 0;
}
next() {
const node = this.stack.pop();
if (node.right) {
this.stack.push(node.right);
}
if (node.left) {
this.stack.push(node.left);
}
return node.value;
}
}
// Binary Tree
class BinaryTree {
constructor() {
this.root = null;
}
insert(value) {
const node = new TreeNode(value);
if (!this.root) {
this.root = node;
return;
}
let current = this.root;
while (true) {
if (value < current.value) {
if (!current.left) {
current.left = node;
break;
}
current = current.left;
} else {
if (!current.right) {
current.right = node;
break;
}
current = current.right;
}
}
}
getInOrderIterator() {
return new InOrderIterator(this.root);
}
getPreOrderIterator() {
return new PreOrderIterator(this.root);
}
}
// Usage
const tree = new BinaryTree();
tree.insert(4);
tree.insert(2);
tree.insert(6);
tree.insert(1);
tree.insert(3);
tree.insert(5);
tree.insert(7);
console.log("In-order traversal:");
const inOrderIterator = tree.getInOrderIterator();
while (inOrderIterator.hasNext()) {
console.log(inOrderIterator.next());
}
console.log("\nPre-order traversal:");
const preOrderIterator = tree.getPreOrderIterator();
while (preOrderIterator.hasNext()) {
console.log(preOrderIterator.next());
}
3. Triển khai trong TypeScript
3.1 Ví dụ về Generic Iterator
// Iterator interface
interface Iterator<T> {
hasNext(): boolean;
next(): T;
current(): T;
reset(): void;
}
// Aggregate interface
interface Aggregate<T> {
getIterator(): Iterator<T>;
}
// Concrete Iterator
class ListIterator<T> implements Iterator<T> {
private position: number = 0;
constructor(private collection: T[]) {}
hasNext(): boolean {
return this.position < this.collection.length;
}
next(): T {
return this.collection[this.position++];
}
current(): T {
return this.collection[this.position];
}
reset(): void {
this.position = 0;
}
}
// Concrete Aggregate
class List<T> implements Aggregate<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
getIterator(): Iterator<T> {
return new ListIterator<T>(this.items);
}
}
// Usage with different types
interface User {
id: number;
name: string;
}
const numberList = new List<number>();
numberList.add(1);
numberList.add(2);
numberList.add(3);
const numberIterator = numberList.getIterator();
while (numberIterator.hasNext()) {
console.log(numberIterator.next());
}
const userList = new List<User>();
userList.add({ id: 1, name: "John" });
userList.add({ id: 2, name: "Jane" });
userList.add({ id: 3, name: "Bob" });
const userIterator = userList.getIterator();
while (userIterator.hasNext()) {
const user = userIterator.next();
console.log(`${user.id}: ${user.name}`);
}
3.2 Ví dụ về Matrix Iterator
// Matrix interface
interface Matrix<T> {
get(row: number, col: number): T;
set(row: number, col: number, value: T): void;
getRows(): number;
getCols(): number;
}
// Matrix Iterator interface
interface MatrixIterator<T> {
hasNext(): boolean;
next(): T;
}
// Matrix implementations
class SimpleMatrix<T> implements Matrix<T> {
private data: T[][];
constructor(rows: number, cols: number) {
this.data = Array(rows)
.fill(null)
.map(() => Array(cols).fill(null));
}
get(row: number, col: number): T {
return this.data[row][col];
}
set(row: number, col: number, value: T): void {
this.data[row][col] = value;
}
getRows(): number {
return this.data.length;
}
getCols(): number {
return this.data[0].length;
}
}
// Row-wise Iterator
class RowWiseIterator<T> implements MatrixIterator<T> {
private currentRow: number = 0;
private currentCol: number = 0;
constructor(private matrix: Matrix<T>) {}
hasNext(): boolean {
return this.currentRow < this.matrix.getRows();
}
next(): T {
const value = this.matrix.get(this.currentRow, this.currentCol);
this.currentCol++;
if (this.currentCol >= this.matrix.getCols()) {
this.currentCol = 0;
this.currentRow++;
}
return value;
}
}
// Column-wise Iterator
class ColumnWiseIterator<T> implements MatrixIterator<T> {
private currentRow: number = 0;
private currentCol: number = 0;
constructor(private matrix: Matrix<T>) {}
hasNext(): boolean {
return this.currentCol < this.matrix.getCols();
}
next(): T {
const value = this.matrix.get(this.currentRow, this.currentCol);
this.currentRow++;
if (this.currentRow >= this.matrix.getRows()) {
this.currentRow = 0;
this.currentCol++;
}
return value;
}
}
// Matrix with iterators
class IterableMatrix<T> extends SimpleMatrix<T> {
getRowWiseIterator(): MatrixIterator<T> {
return new RowWiseIterator<T>(this);
}
getColumnWiseIterator(): MatrixIterator<T> {
return new ColumnWiseIterator<T>(this);
}
}
// Usage
const matrix = new IterableMatrix<number>(3, 3);
// Fill matrix
let value = 1;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
matrix.set(i, j, value++);
}
}
console.log("Row-wise iteration:");
const rowIterator = matrix.getRowWiseIterator();
while (rowIterator.hasNext()) {
console.log(rowIterator.next());
}
console.log("\nColumn-wise iteration:");
const colIterator = matrix.getColumnWiseIterator();
while (colIterator.hasNext()) {
console.log(colIterator.next());
}
4. Trường hợp sử dụng thực tế
4.1 Custom Collection với Filtering Iterator
interface Product {
id: number;
name: string;
price: number;
category: string;
}
class FilteringIterator<T> implements Iterator<T> {
private filteredItems: T[];
private position: number = 0;
constructor(
items: T[],
private predicate: (item: T) => boolean
) {
this.filteredItems = items.filter(predicate);
}
hasNext(): boolean {
return this.position < this.filteredItems.length;
}
next(): T {
return this.filteredItems[this.position++];
}
current(): T {
return this.filteredItems[this.position];
}
reset(): void {
this.position = 0;
}
}
class ProductCollection implements Aggregate<Product> {
private products: Product[] = [];
addProduct(product: Product): void {
this.products.push(product);
}
getIterator(): Iterator<Product> {
return new ListIterator<Product>(this.products);
}
getPriceFilterIterator(minPrice: number): Iterator<Product> {
return new FilteringIterator<Product>(
this.products,
(product) => product.price >= minPrice
);
}
getCategoryFilterIterator(category: string): Iterator<Product> {
return new FilteringIterator<Product>(
this.products,
(product) => product.category === category
);
}
}
// Usage
const products = new ProductCollection();
products.addProduct({
id: 1,
name: "Laptop",
price: 1200,
category: "Electronics",
});
products.addProduct({
id: 2,
name: "Smartphone",
price: 800,
category: "Electronics",
});
products.addProduct({ id: 3, name: "Book", price: 20, category: "Books" });
products.addProduct({
id: 4,
name: "Tablet",
price: 500,
category: "Electronics",
});
console.log("Products over $1000:");
const expensiveProducts = products.getPriceFilterIterator(1000);
while (expensiveProducts.hasNext()) {
const product = expensiveProducts.next();
console.log(`${product.name}: $${product.price}`);
}
console.log("\nElectronics products:");
const electronicsProducts = products.getCategoryFilterIterator("Electronics");
while (electronicsProducts.hasNext()) {
const product = electronicsProducts.next();
console.log(`${product.name}: ${product.category}`);
}
4.2 File System Iterator
interface FileSystemNode {
name: string;
isDirectory(): boolean;
getSize(): number;
}
class File implements FileSystemNode {
constructor(
public name: string,
private size: number
) {}
isDirectory(): boolean {
return false;
}
getSize(): number {
return this.size;
}
}
class Directory implements FileSystemNode {
private children: FileSystemNode[] = [];
constructor(public name: string) {}
add(node: FileSystemNode): void {
this.children.push(node);
}
isDirectory(): boolean {
return true;
}
getSize(): number {
return this.children.reduce((sum, node) => sum + node.getSize(), 0);
}
getChildren(): FileSystemNode[] {
return this.children;
}
}
// Depth-First Iterator
class DepthFirstIterator implements Iterator<FileSystemNode> {
private stack: FileSystemNode[] = [];
constructor(root: FileSystemNode) {
this.stack.push(root);
}
hasNext(): boolean {
return this.stack.length > 0;
}
next(): FileSystemNode {
if (!this.hasNext()) {
throw new Error("No more elements");
}
const node = this.stack.pop()!;
if (node.isDirectory()) {
const directory = node as Directory;
const children = directory.getChildren();
for (let i = children.length - 1; i >= 0; i--) {
this.stack.push(children[i]);
}
}
return node;
}
current(): FileSystemNode {
return this.stack[this.stack.length - 1];
}
reset(): void {
this.stack = [];
}
}
// Usage
const root = new Directory("root");
const docs = new Directory("docs");
docs.add(new File("readme.md", 100));
docs.add(new File("contributing.md", 200));
const src = new Directory("src");
src.add(new File("index.ts", 300));
src.add(new File("app.ts", 400));
const tests = new Directory("tests");
tests.add(new File("app.test.ts", 500));
src.add(tests);
root.add(docs);
root.add(src);
const iterator = new DepthFirstIterator(root);
while (iterator.hasNext()) {
const node = iterator.next();
const indent = " ".repeat(node.name.split("/").length - 1);
console.log(`${indent}${node.name} (${node.getSize()} bytes)`);
}
4.5 Iterator native trong JavaScript hiện đại
Trong JS/TS 2025+, hiếm khi phải viết class iterator tay — có 3 công cụ sẵn:
4.5.1 Iterable / Iterator protocol
Bất kỳ object nào có [Symbol.iterator]() trả về { next(): { value, done } } đều dùng được với for…of, spread, destructure, Array.from:
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current < end
? { value: current++, done: false }
: { value: undefined, done: true };
},
};
}
}
const r = new NumberRange(1, 5);
for (const n of r) console.log(n); // 1 2 3 4
console.log([...r]); // [1, 2, 3, 4]
console.log(Array.from(r)); // [1, 2, 3, 4]
4.5.2 Generator — viết iterator rất gọn
Generator (function*) là cách ngắn nhất để tạo iterator:
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) yield i;
}
console.log([...range(0, 10, 2)]); // [0, 2, 4, 6, 8]
// Lazy pipeline — không bao giờ tạo mảng trung gian
function* map(iter, fn) {
for (const x of iter) yield fn(x);
}
function* filter(iter, pred) {
for (const x of iter) if (pred(x)) yield x;
}
function* take(iter, n) {
let i = 0;
for (const x of iter) {
if (i++ >= n) return;
yield x;
}
}
const first3Even = [
...take(
filter(
map(range(0, 1000), (x) => x * x),
(x) => x % 2 === 0
),
3
),
];
// [0, 4, 16]
4.5.3 Iterator Helpers — Stage 4 (ES2025)
ES2025 bổ sung method chain trực tiếp trên iterator (như array nhưng lazy). Chrome 122+, Firefox 131+, Node 22+ đã hỗ trợ native; dự án cũ có thể polyfill core-js.
function* naturals() {
let n = 0;
while (true) yield n++;
}
const result = naturals()
.map((x) => x * x)
.filter((x) => x % 2 === 0)
.take(5)
.toArray();
console.log(result); // [0, 4, 16, 36, 64]
So với ví dụ 4.5.2, phiên bản này ngắn hơn, lazy chuẩn, và không cần helper thủ công.
4.5.4 Async Iterator — for await...of
Khi data đến dần theo thời gian (stream HTTP, paginated API, WebSocket, file read…), dùng async function* hoặc implement [Symbol.asyncIterator]():
async function* paginate(url, pageSize = 50) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}&size=${pageSize}`);
const items = await res.json();
if (items.length === 0) return;
for (const item of items) yield item;
page++;
}
}
// Tiêu thụ stream lazy — chỉ fetch page khi consumer cần
for await (const user of paginate("/api/users")) {
console.log(user.id);
if (user.role === "admin") break; // dừng sớm → không fetch trang sau nữa
}
Bạn cũng có thể lặp trực tiếp trên ReadableStream (Web Streams API):
const res = await fetch("/api/large.ndjson");
for await (const chunk of res.body) {
// chunk là Uint8Array — xử lý dần, không cần buffer toàn bộ vào RAM
}
4.5.5 Khi nào dùng class iterator cổ điển (mục 2, 3)?
- Cần nhiều mode duyệt trên cùng collection (BFS vs DFS, forward vs reverse) — dễ đóng gói bằng class.
- Viết thư viện cho team chưa quen generator.
- Ngôn ngữ/runtime cũ không hỗ trợ generator (rất hiếm 2025).
Phần lớn code ứng dụng thực tế → ưu tiên generator + iterator helpers.
5. Ưu điểm và Nhược điểm
5.1 Ưu điểm
- Nguyên tắc Single Responsibility: Tách biệt logic duyệt khỏi tập hợp.
- Open/Closed Principle: Dễ dàng thêm các iterator mới mà không thay đổi tập hợp.
- Đơn giản hóa interface: Client không cần biết về cấu trúc bên trong của tập hợp.
5.2 Nhược điểm
- Phức tạp hóa: Với tập hợp đơn giản, việc sử dụng iterator có thể là thừa.
- Hiệu năng: Một số triển khai có thể kém hiệu quả hơn duyệt trực tiếp.
- Quản lý trạng thái: Cần lưu ý về trạng thái của iterator trong môi trường đa luồng.
6. Kết luận
Iterator Pattern là một công cụ mạnh mẽ cho việc duyệt qua các tập hợp phức tạp một cách thống nhất. Pattern này đặc biệt hữu ích khi làm việc với các cấu trúc dữ liệu phức tạp hoặc khi cần nhiều cách duyệt khác nhau trên cùng một tập hợp. JavaScript và TypeScript cung cấp nhiều tính năng tích hợp cho iterator, nhưng việc hiểu và triển khai pattern này vẫn quan trọng cho việc xây dựng các giải pháp tùy chỉnh.