跳至主要内容

[JS] JavaScript 疊代器(Iterator)

觀念

  • 在 ES6 中,有三種資料結構原生就具備 Iterator 接口:ArrayArray-like ObjectSetMap 結構
  • 除了上述這些之外,其他資料結構(主要是物件)都需要自己在 Symbol.iterator 屬性上實作 iterator,這樣才會被 for...of 迴圈遍歷。
  • iterator 會返回 value(當前成員的值) 和 done(Boolean,表示是否遍歷結束)

只要有在 Symbol.iterator 這個屬性上實作 iterator 的 interface,就表示這個資料結構是 iterable,也就是可以使用 for...of 來遍歷:

/**
* Iterator 可以透過 Symbol.iterator 來客制化疊代的方式
* 裡面會包含 next function,這個 function 會回傳 value 和 done
* 像這樣:
**/

type IteratorFn = () => {
next(): { value: any; done: boolean };
};

type Iterator = {
[Symbol.iterator]: IteratorFn;
};

基本使用

Symbol 時有提到 JS 中有內建的 Symbol,大部分是提供內建的函式方法(Well-known Symbols),其中 Symbol.iterator 就是裡面內的 Symbol,對於可疊代的型別,在 Symbol.iterator 中就有提供函式可以讓它在 for...of 時使用。

/**
* Demonstrate an iterator
**/

let arr = [1, 2, 3];
console.log(typeof arr[Symbol.iterator]); // function

let current = arr[Symbol.iterator]();
current.next(); // Object {value: 1, done: false}
current.next(); // Object {value: 2, done: false}
current.next(); // Object {value: 3, done: false}
current.next(); // Object {value: undefined, done: true}

模擬 iterator 的 function

/**
* Create a function to simulate iterator
**/
var it = makeIterator(['a', 'b']);

it.next(); // { value: "a", done: false }
it.next(); // { value: "b", done: false }
it.next(); // { value: undefined, done: true }

function makeIterator(array) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < array.length
? { value: array[nextIndex++], done: false }
: { value: undefined, done: true };
},
};
}

搭配 for...of,客制化自己的 iterator

只要有在 Symbol.iterator 實作 iterator 的 interface,它就是 iterable,即可被 for...of 所迭代。

for...of 在當 Iterator 的 donetrue 時就不再疊代。

Array 部分

/**
* Create a custom iterable array
**/
arr[Symbol.iterator] = function () {
let nextIndex = 0;
return {
next() {
return nextIndex < arr.length
? {
value: arr[nextIndex++] * 2,
done: false,
}
: {
value: undefined,
done: true,
};
},
};
};
for (item of arr) {
// 當 done 為 true 時就不在疊代
console.log(item); // 2, 4, 6
}

物件部分

/**
* Create a custom iterable Object
**/

let person = {
firstName: 'Aaron',
lastName: 'Chen',
hobbies: ['computer', 'programming', 'sports'],

// 讓 JS 知道這個物件具有 iterator,所以是 iterable
[Symbol.iterator]: function () {
let index = 0;
let hobbies = this.hobbies;
return {
next() {
return index < hobbies.length
? {
done: false,
value: hobbies[index++],
}
: {
done: true,
value: undefined,
};
},
};
},
};

for (let hobby of person) {
console.log(hobby); // computer, programming, spots
}

也可以使用 Class:

type IteratorFn = () => {
next(): { value: any; done: boolean };
};

type Iterator = {
[Symbol.iterator]: IteratorFn;
};

class Countdown implements Iterator {
#start: number;
constructor(start: number) {
this.#start = start;
}

[Symbol.iterator] = function () {
let current = this.#start;

return {
next() {
if (current >= 0) {
return {
value: current--,
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
},
};
};
}

// 因為 Countdown 有實作 Iterator,所以可以用 for...of 加以迭代
for (let i of new Countdown(3)) {
console.log(i); // 3 2 1 0
}

Demo Code

ES6 Iterator @ PJCHENder JSFiddle

參考資料