Trong JavaScript ES6, sự khác biệt giữa iterable và iterator là gì?


14

Là một iterable giống như một iterator, hay chúng khác nhau?

Dường như, từ các đặc tả , một iterable là một đối tượng, giả sử, objobj[Symbol.iterator]nghĩa là tham chiếu đến một hàm, để khi được gọi, trả về một đối tượng có một nextphương thức có thể trả về một {value: ___, done: ___}đối tượng:

function foo() {
    let i = 0;
    const wah = {
        next: function() {
            if (i <= 2) return { value: (1 + 2 * i++), done: false }
            else return { value: undefined, done: true }
        }
    };
    return wah;     // wah is iterator
}

let bar = {}        // bar is iterable

bar[Symbol.iterator] = foo;

console.log([...bar]);             // [1, 3, 5]   
for (a of bar) console.log(a);     // 1 3 5 (in three lines)

Vì vậy, trong đoạn mã trên, barlà iterable, và wahlà iterator, và next()là giao diện iterator.

Vì vậy, iterable và iterator là những thứ khác nhau.

Tuy nhiên, bây giờ, trong một ví dụ phổ biến về trình tạo và trình lặp:

function* gen1() {
    yield 1;
    yield 3;
    yield 5;
}

const iter1 = gen1();

console.log([...iter1]);                           // [1, 3, 5]
for (a of iter1) console.log(a);                   // nothing

const iter2 = gen1();
for (a of iter2) console.log(a);                   // 1 3 5 (in three lines)

console.log(iter1[Symbol.iterator]() === iter1);   // true

Trong trường hợp trên, gen1là trình tạo, và iter1là trình vòng lặp, và iter1.next()sẽ thực hiện công việc thích hợp. Nhưng iter1[Symbol.iterator]không đưa ra một chức năng mà khi được gọi sẽ trả lại iter1, đó là một trình vòng lặp. Vì vậy, iter1cả hai là một iterable và iterator trong trường hợp này?

Bên cạnh đó, iter1khác với ví dụ 1 ở trên, bởi vì iterable trong ví dụ 1 có thể cung cấp [1, 3, 5]bao nhiêu lần bạn muốn sử dụng [...bar], trong khi đó iter1là một lần lặp, nhưng vì nó trả về chính nó, là cùng một trình lặp mỗi lần, sẽ chỉ cung cấp [1, 3, 5]một lần.

Vì vậy, chúng ta có thể nói, đối với một lần lặp bar, bao nhiêu lần có thể [...bar]cho kết quả [1, 3, 5]- và câu trả lời là, nó phụ thuộc. Và iterable có giống như iterator không? Và câu trả lời là, chúng là những thứ khác nhau, nhưng chúng có thể giống nhau, khi iterable sử dụng chính nó như là iterator. Đúng không?



" Vì vậy, iter1cả hai là một iterable và iterator trong trường hợp này? " - có. Tất cả các trình vòng lặp gốc cũng có thể lặp lại bằng cách tự quay trở lại, để bạn có thể dễ dàng chuyển chúng vào các cấu trúc mong đợi một lần lặp.
Bergi

Câu trả lời:


10

Đúng, iterablesiterators là những thứ khác nhau, nhưng hầu hết các trình lặp (bao gồm tất cả những cái bạn nhận được từ chính JavaScript, như từ keyshoặc valuescác phương thức trên Array.prototypehoặc các trình tạo từ các hàm của trình tạo) đều thừa hưởng từ đối tượng% IteratorPrototype% , có một Symbol.iteratorphương thức như điều này:

[Symbol.iterator]() {
    return this;
}

Kết quả là tất cả các trình vòng lặp tiêu chuẩn cũng là các vòng lặp. Đó là vì vậy bạn có thể sử dụng chúng trực tiếp hoặc sử dụng chúng trong for-ofcác vòng lặp và như vậy (mong đợi các vòng lặp, không phải các vòng lặp).

Xem xét keysphương thức của mảng: Nó trả về một trình vòng lặp mảng truy cập các khóa của mảng (chỉ mục của nó, dưới dạng số). Lưu ý rằng nó trả về một iterator . Nhưng một cách sử dụng phổ biến của nó là:

for (const index of someArray.keys()) {
    // ...
}

for-ofmất một iterable , không phải iterator , vậy tại sao nó lại hoạt động?

Nó hoạt động vì iterator cũng lặp lại; Symbol.iteratorchỉ trở về this.

Đây là một ví dụ tôi sử dụng trong Chương 6 của cuốn sách của mình: Nếu bạn muốn lặp qua tất cả các mục nhưng bỏ qua mục đầu tiên và bạn không muốn sử dụng sliceđể cắt bỏ tập hợp con, bạn có thể lấy iterator, đọc giá trị đầu tiên, sau đó đưa ra một for-ofvòng lặp:

const a = ["one", "two", "three", "four"];
const it = a[Symbol.iterator]();
// Skip the first one
it.next();
// Loop through the rest
for (const value of it) {
    console.log(value);
}

Lưu ý rằng đây là tất cả các trình vòng lặp tiêu chuẩn . Thỉnh thoảng mọi người cho thấy các ví dụ về các trình vòng lặp được mã hóa thủ công như thế này:

Các iterator được trả về bởi rangekhông một iterable, vì vậy nó không thành công khi chúng ta cố gắng sử dụng nó với for-of.

Để làm cho nó có thể lặp lại, chúng ta cần phải:

  1. Thêm Symbol.iteratorphương thức ở đầu câu trả lời ở trên, hoặc
  2. Làm cho nó kế thừa từ% IteratorPrototype%, đã có phương thức đó

Đáng buồn thay, TC39 quyết định không cung cấp một cách trực tiếp để có được đối tượng% IteratorPrototype%. Có một cách gián tiếp (lấy iterator từ một mảng, sau đó lấy nguyên mẫu của nó, được định nghĩa là% IteratorPrototype%), nhưng đó là một nỗi đau.

Nhưng dù sao cũng không cần phải viết các trình lặp như thế; chỉ cần sử dụng hàm tạo, vì trình tạo mà nó trả về có thể lặp lại:


Ngược lại, không phải tất cả các lần lặp đều là các trình vòng lặp. Mảng là lặp, nhưng không lặp. Chuỗi, Bản đồ và Bộ cũng vậy.


0

Tôi thấy rằng có một số định nghĩa chính xác hơn về các thuật ngữ và đây là những câu trả lời dứt khoát hơn:

Theo Thông số kỹ thuật ES6MDN :

Khi chúng ta có

function* foo() {   // note the "*"
    yield 1;
    yield 3;
    yield 5;
}

foođược gọi là hàm tạo . Và khi chúng ta có

let bar = foo();

barlà một đối tượng máy phát điện . Và một đối tượng trình tạo phù hợp với cả giao thức lặp và giao thức lặp .

Phiên bản đơn giản hơn là giao diện iterator, đây chỉ là một .next()phương thức.

Giao thức lặp là: đối với đối tượng obj, obj[Symbol.iterator]đưa ra "hàm đối số không trả về một đối tượng, tuân thủ giao thức lặp".

Theo tiêu đề của liên kết MDN , có vẻ như chúng ta cũng có thể gọi một đối tượng trình tạo là "trình tạo".

Lưu ý rằng trong cuốn sách Hiểu về ECMAScript 6 của Nicolas Zakas , có lẽ ông đã gọi một cách lỏng lẻo là "hàm tạo" là "trình tạo" và "đối tượng trình tạo" là "trình lặp". Điểm lấy đi là, chúng thực sự có liên quan đến cả hai "trình tạo" - một là hàm tạo và một là đối tượng của trình tạo hoặc trình tạo. Đối tượng trình tạo phù hợp với cả giao thức lặp và giao thức lặp.

Nếu nó chỉ là một đối tượng tuân thủ giao thức iterator , bạn không thể sử dụng [...iter]hoặc for (a of iter). Nó phải là một đối tượng phù hợp với giao thức lặp .

Và sau đó, cũng có một lớp Iterator mới, trong một thông số kỹ thuật JavaScript trong tương lai vẫn còn trong một bản nháp . Nó có một giao diện lớn hơn, bao gồm các phương pháp như forEach, map, reducecủa giao diện mảng hiện tại, và những cái mới, chẳng hạn như và take, và drop. Trình vòng lặp hiện tại đề cập đến đối tượng chỉ với nextgiao diện.

Để trả lời câu hỏi ban đầu: sự khác biệt giữa một iterator và một iterable là gì, câu trả lời là: một iterator là một đối tượng với giao diện .next(), và một iterable là một đối tượng objnhư vậy mà obj[Symbol.iterator]có thể cung cấp một chức năng zero-luận rằng, khi gọi, trả về một trình vòng lặp.

Và một trình tạo là cả iterable và iterator, để thêm vào đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.