Đối tượng lặp(Iterable) là một sự khái quát của mảng. Đó là một khái niệm cho phép chúng ta làm cho bất kỳ đối tượng nào đó có thể sử dụng được trong một vòng lặp for..of.

Tất nhiên, Mảng là lặp đi lặp lại. Nhưng có nhiều đối tượng tích hợp khác, cũng có thể lặp lại. Ví dụ, chuỗi cũng có thể lặp lại.

Nếu một đối tượng về mặt kỹ thuật không phải là một mảng, nhưng đại diện cho một bộ sưu tập (danh sách, tập hợp) của một cái gì đó, thì nó for..oflà một cú pháp tuyệt vời để lặp lại nó, vì vậy hãy xem cách làm cho nó hoạt động.

1. Symbol.iterator

Chúng ta có thể dễ dàng nắm bắt khái niệm lặp đi lặp lại bằng cách tạo một trong số chúng.

Chẳng hạn, chúng ta có một đối tượng không phải là một mảng, nhưng có vẻ phù hợp với for..of.

Giống như một đối tượng range đại diện cho một khoảng số:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

let range = {
  from: 1,
  to: 5
};

// We want the for..of to work:
// for(let num of range) ... num=1,2,3,4,5

Để làm cho range có thể lặp (và do đó cho phép sử dụngfor..of), chúng ta cần thêm một phương thức vào đối tượng có tên Symbol.iterator(một biểu tượng tích hợp đặc biệt chỉ dành cho làm điều đó).

  1. Khi for..ofbắt đầu, nó gọi phương thức đó một lần (hoặc lỗi nếu không tìm thấy). Phương thức phải trả về một iterator – một đối tượng với phương thức next.
  2. chỉfor..of hoạt động và trả lại đối tượng.
  3. Khi for..ofmuốn giá trị tiếp theo, nó gọi next()đối tượng đó.
  4. Kết quả next()phải có dạng {done: Boolean, value: any}, trong đó done=true có nghĩa là phép lặp được kết thúc, nếu không valuelà giá trị tiếp theo.

Đây là cách thực hiện đầy đủ rangevới các comment:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

let range = {
  from: 1,
  to: 5
};

// 1. call to for..of initially calls this
range[Symbol.iterator] = function() {

  // ...it returns the iterator object:
  // 2. Onward, for..of works only with this iterator, asking it for next values
  return {
    current: this.from,
    last: this.to,

    // 3. next() is called on each iteration by the for..of loop
    next() {
      // 4. it should return the value as an object {done:.., value :...}
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// now it works!
for (let num of range) {
  alert(num); // 1, then 2, 3, 4, 5
}

Xin lưu ý tính năng cốt lõi của iterables: tách mối quan tâm.

  • Bản thân range nó không có phương thứcnext().
  • Thay vào đó, một đối tượng khác, gọi là iterator ‘được tạo bởi lệnh gọi range[Symbol.iterator]()và nó cónext()tạo ra các giá trị cho phép lặp.

Vì vậy, đối tượng iterator tách biệt với đối tượng mà nó lặp đi lặp lại.

Về mặt kỹ thuật, chúng ta có thể hợp nhất chúng và sử dụng rangechính nó như là vòng lặp để làm cho code đơn giản hơn.

Như thế này:

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
}

Bây giờ range[Symbol.iterator]()trả về rangechính đối tượng: nó có next()phương thức cần thiết và ghi nhớ tiến trình lặp hiện tại trong this.current. Ngắn hơn? Đúng. Và đôi khi điều đó cũng tốt.

Nhược điểm là bây giờ không thể có hai for..ofvòng chạy trên đối tượng cùng một lúc: chúng sẽ chia sẻ trạng thái lặp, bởi vì chỉ có một vòng lặp – chính đối tượng đó. Nhưng hai for-of song song là một điều hiếm, ngay cả trong các script không đồng bộ.

Lặp đi lặp lại vô hạn

Lặp đi lặp lại vô hạn cũng có thể. Ví dụ, rangetrở thành vô hạn cho range.to = Infinity. Hoặc chúng ta có thể tạo một đối tượng có thể lặp lại để tạo ra một chuỗi vô số các số giả ngẫu nhiên. Cũng có thể hữu ích.

Không có giới hạn nào dừngnext, nó có thể trả về ngày càng nhiều giá trị, đó là điều bình thường.

Tất nhiên, vòng lặp for..of trên một vòng lặp như vậy sẽ là vô tận. Nhưng chúng ta luôn có thể ngăn chặn nó bằng cách sử dụng break.

2. Chuỗi có thể lặp lại

Mảng và chuỗi được lặp lại được sử dụng rộng rãi nhất.

Đối với một chuỗi, vòng lặpfor..of trên các ký tự của nó:

for (let char of "test") {
  // triggers 4 times: once for each character
  alert( char ); // t, then e, then s, then t
}

Và nó hoạt động khá chính xác!

let str = '𝒳😂';
for (let char of str) {
    alert( char ); // 𝒳, and then 😂
}

3. Gọi một trình vòng lặp rõ ràng

Để hiểu sâu hơn, hãy xem cách sử dụng một trình vòng lặp một cách rõ ràng.

Chúng ta sẽ lặp lại một chuỗi theo cách chính xác như for..of, nhưng với các cuộc gọi trực tiếp. Code này tạo ra một trình vòng lặp chuỗi và nhận các giá trị từ nó.

let str = "Hello";

// does the same as
// for (let char of str) alert(char);

let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value); // outputs characters one by one
}

Điều đó hiếm khi cần thiết, nhưng cho chúng ta nhiều quyền kiểm soát quá trình hơn for..of. Chẳng hạn, chúng ta có thể phân chia quá trình lặp đi lặp lại một chút, sau đó dừng lại, làm một cái gì đó khác, và sau đó tiếp tục lại sau.

4. Lặp lại và Một cái gì đó giống mảng(Iterables and array-likes)

Có hai thuật ngữ chính thức trông giống nhau, nhưng rất khác nhau. Hãy chắc chắn rằng bạn hiểu rõ về chúng để tránh nhầm lẫn.

  • Lặp lại(Iterable) là các đối tượng thực hiện phương thức Symbol.iterator, như được mô tả ở trên.
  • Cái gì đó giống mảng là các đối tượng có chỉ mục và lengthvì vậy chúng trông giống như mảng.

Khi chúng ta sử dụng JavaScript cho các tác vụ thực tế trong trình duyệt hoặc các môi trường khác, chúng ta có thể gặp các đối tượng là iterables hoặc Cái gì đó giống mảng hoặc cả hai.

Chẳng hạn, các chuỗi đều có thể lặp lại ( for..ofhoạt động trên chúng) và nó giống như mảng (chúng có các chỉ mục số và length).

Nhưng một iterable có thể không giống như mảng. Và ngược lại, một mảng giống như có thể không lặp lại được.

Ví dụ, rangetrong ví dụ trên có thể lặp lại, nhưng không giống như mảng, vì nó không có thuộc tính được lập chỉ mục và length.

Và đây là đối tượng giống như mảng, nhưng không thể lặp lại:

let arrayLike = { // has indexes and length => array-like
  0: "Hello",
  1: "World",
  length: 2
};

// Error (no Symbol.iterator)
for (let item of arrayLike) {}

Cả iterables và kiểu giống mảng thường không phải là mảng , chúng không có push, popv.v. Điều đó khá bất tiện nếu chúng ta có một đối tượng như vậy và muốn làm việc với nó như với một mảng. Ví dụ, chúng ta muốn làm việc với việc rangesử dụng các phương thức mảng. Làm thế nào để đạt được điều đó?

5. Array.from

Có một phương thức phổ biến Array.from lấy một giá trị lặp hoặc giống như mảng và tạo ra một Real Array thực sự từ nó. Sau đó chúng ta có thể gọi các phương thức mảng trên nó.

Ví dụ:

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};

let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (method works)

Array.fromtại dòng (*)lấy đối tượng, kiểm tra xem nó có phải là một lần lặp hoặc giống như mảng không, sau đó tạo một mảng mới và sao chép tất cả các mục vào nó.

Điều tương tự cũng xảy ra đối với một lần lặp:

// assuming that range is taken from the example above
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (array toString conversion works)

Cú pháp đầy đủ cho Array.fromcũng cho phép chúng ta cung cấp một chức năng ánh xạ với tùy chọn:

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

Đối số thứ hai tùy chọn mapFncó thể là một hàm sẽ được áp dụng cho từng phần tử trước khi thêm nó vào mảng và thisArgcho phép chúng ta đặt thischo nó.

Ví dụ:

// assuming that range is taken from the example above

// square each number
let arr = Array.from(range, num => num * num);

alert(arr); // 1,4,9,16,25

Ở đây chúng ta sử dụng Array.fromđể biến một chuỗi thành một mảng các ký tự:

let str = '𝒳😂';

// splits str into array of characters
let chars = Array.from(str);

alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2

Không giống như str.split, nó dựa vào tính chất lặp của chuỗi và do đó, giống như for..of hoạt động chính xác với các cặp thay thế.

Về mặt kỹ thuật, nó hoạt động giống như:

let str = '𝒳😂';

let chars = []; // Array.from internally does the same loop
for (let char of str) {
  chars.push(char);
}

alert(chars);

Nhưng nó ngắn hơn.

Chúng ta thậm chí có thể sử dụng slice cho nó:

function slice(str, start, end) {
  return Array.from(str).slice(start, end).join('');
}

let str = '𝒳😂𩷶';

alert( slice(str, 1, 3) ); // 😂𩷶

// the native method does not support surrogate pairs
alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs)

6. Tóm lược

Các đối tượng có thể được sử dụng trong for..ofđược gọi là iterable .

  • Về mặt kỹ thuật, iterables phải thực hiện phương thức được đặt tên Symbol.iterator.
    • Kết quả của obj[Symbol.iterator]được gọi là một vòng lặp . Nó xử lý quá trình lặp đi lặp lại.
    • Một trình vòng lặp phải có phương thức được đặt tên next()trả về một đối tượng {done: Boolean, value: any}, ở đây done:truebiểu thị sự kết thúc của quá trình lặp, nếu không thì valuelà giá trị tiếp theo.
  • Các Symbol.iteratorphương pháp được gọi tự động bằng cách for..of, nhưng chúng ta cũng có thể làm điều đó trực tiếp.
  • Các iterables tích hợp như chuỗi hoặc mảng, cũng thực hiện Symbol.iterator.
  • Chuỗi lặp biết về các cặp thay thế.

Các đối tượng có thuộc tính được lập chỉ mục(index) và lengthđược gọi là giống như mảng . Các đối tượng như vậy cũng có thể có các thuộc tính và phương thức khác, nhưng thiếu các phương thức tích hợp của mảng.

Nếu chúng ta nhìn vào bên trong đặc tả – chúng ta sẽ thấy rằng hầu hết các phương thức tích hợp đều cho rằng chúng hoạt động với các lần lặp hoặc cái giống như mảng thay vì các mảng thực tế, vì nó trừu tượng hơn.

Array.from(obj[, mapFn, thisArg])tạo ra một Arraythực tế với một lặp đi lặp lại hoặc giống như mảng obj, và sau đó chúng ta có thể sử dụng các phương thức mảng trên nó. Các đối số tùy chọn mapFnthisArgcho phép chúng ta áp dụng một hàm cho từng mục.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!