JavaScript: Iterators


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

Vấn đề của vòng lặp for

Khi bạn có một mảng dữ liệu, bạn thường sử dụng vòng lặp for để lặp lại các phần tử của nó. Ví dụ:

let ranks = ['A', 'B', 'C'];

for (let i = 0; i < ranks.length; i++) {
    console.log(ranks[i]);
}

Vòng lặp for sử dụng biến để theo dõi chỉ mục của mảng ranks. Giá trị tăng dần mỗi lần vòng lặp thực thi miễn là giá trị của nhỏ hơn số phần tử trong mảng ranks.

Mã này rất đơn giản. Tuy nhiên, độ phức tạp của nó tăng lên khi bạn lồng một vòng lặp vào trong một vòng lặp khác. Ngoài ra, việc theo dõi nhiều biến bên trong vòng lặp dễ xảy ra lỗi.

ES6 đã giới thiệu một cấu trúc vòng lặp mới được gọi là for...of để loại bỏ độ phức tạp của vòng lặp tiêu chuẩn và tránh các lỗi gây ra khi theo dõi các chỉ mục vòng lặp.

Để lặp lại các phần tử của mảng ranks, bạn sử dụng cấu trúc  for...of:

for(let rank of ranks) {
    console.log(rank);
}

Sử dụng for...of thanh lịch hơn nhiều so với vòng lặp for vì nó thể hiện mục đích thực sự của mã - lặp qua một mảng để truy cập từng phần tử trong chuỗi.

Trên hết, for...of có khả năng tạo vòng lặp trên bất kỳ đối tượng có thể lặp lại nào , không chỉ một mảng.

Để hiểu đối tượng lặp, trước tiên bạn cần hiểu các giao thức lặp

Iteration protocols

Có hai giao thức lặp: iterable protocol và iterator protocol .

Iterator protocol

Một đối tượng là một trình vòng lặp (iterator) khi nó triển khai một giao diện (hoặc API) trả lời hai câu hỏi:

  • Có phần tử nào còn sót lại không?
  • Nếu có thì phần tử đó là gì?

Về mặt kỹ thuật, một đối tượng được coi là một trình vòng lặp (iterator) khi nó có một phương thức next() trả về một đối tượng có hai thuộc tính:

  •  done: một giá trị boolean cho biết liệu có thêm phần tử nào có thể được lặp lại hay không.
  •  value: phần tử hiện tại.

Mỗi lần bạn gọi next(), nó sẽ trả về giá trị tiếp theo trong bộ sưu tập:

{ value: 'next value', done: false }

Nếu bạn gọi phương thức next() sau khi giá trị cuối cùng được trả về, thì next() sẽ trả về kết quả đối tượng như sau:

{done: true: value: undefined}

Giá trị của thuộc tính done cho biết rằng không còn giá trị nào để trả về và giá trị value được đặt thành undefined.

Iterable protocol

Một đối tượng có thể lặp lại khi nó chứa một phương thức được gọi [Symbol.iterator] không có đối số và trả về một đối tượng tuân theo giao thức lặp.

Một trong những synbol nổi tiếng được tích hợp sẵn trong ES6 là [Symbol.iterator].

Iterators

Vì ES6 cung cấp các trình vòng lặp tích hợp cho các kiểu bộ sưu tập  Array, Set và Map, nên bạn không phải tạo các trình vòng lặp cho các đối tượng này.

Nếu bạn có một loại tùy chỉnh và muốn làm cho nó có thể lặp lại để có thể sử dụng cấu trúc vòng lặp for...of, bạn cần triển khai các giao thức lặp.

Đoạn mã sau tạo một đối tượng Sequence trả về danh sách các số trong phạm vi (startend) với một interval số nằm giữa các số tiếp theo.

class Sequence {
    constructor( start = 0, end = Infinity, interval = 1 ) {
        this.start = start;
        this.end = end;
        this.interval = interval;
    }
    [Symbol.iterator]() {
        let counter = 0;
        let nextIndex = this.start;
        return  {
            next: () => {
                if ( nextIndex <= this.end ) {
                    let result = { value: nextIndex,  done: false }
                    nextIndex += this.interval;
                    counter++;
                    return result;
                }
                return { value: counter, done: true };
            }
        }
    }
};

Đoạn mã sau sử dụng vòng lặp for...of để lặp qua Sequence :

let evenNumbers = new Sequence(2, 10, 2);

for (const num of evenNumbers) {
    console.log(num);
}

Output:

2
4
6
8
10

Bạn có thể truy cập phương thức [Symbol.iterator]() rõ ràng như trong đoạn script sau:

let evenNumbers = new Sequence(2, 10, 2);
let iterator = evenNumbers[Symbol.iterator]();

let result = iterator.next();

while( !result.done ) {
    console.log(result.value);
    result = iterator.next();
}

Cleaning up

Ngoài phương thức  next()[Symbol.iterator]() có thể tùy ý trả về một phương thức có tên return().

Phương thức return() được gọi tự động khi quá trình lặp bị dừng sớm. Đó là nơi bạn có thể đặt mã để dọn sạch tài nguyên.

Ví dụ sau triển khai phương thức return() cho đối tượng Sequence:

class Sequence {
    constructor( start = 0, end = Infinity, interval = 1 ) {
        this.start = start;
        this.end = end;
        this.interval = interval;
    }
    [Symbol.iterator]() {
        let counter = 0;
        let nextIndex = this.start;
        return  {
            next: () => {
                if ( nextIndex <= this.end ) {
                    let result = { value: nextIndex,  done: false }
                    nextIndex += this.interval;
                    counter++;
                    return result;
                }
                return { value: counter, done: true };
            },
            return: () => {
                console.log('cleaning up...');
                return { value: undefined, done: true };
            }
        }
    }
}

Đoạn mã sau sử dụng đối tượng Sequence này để tạo ra một chuỗi các số lẻ từ 1 đến 10. Tuy nhiên, nó sẽ dừng quá trình lặp lại sớm. Kết quả là phương thức return() được tự động gọi.

let oddNumbers = new Sequence(1, 10, 2);

for (const num of oddNumbers) {
    if( num > 7 ) {
        break;
    }
    console.log(num);
}

Output:

1
3
5
7
cleaning up...
» Tiếp: Class
« Trước: Các hàm mũi tên ES6 trong JavaScript
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!