ES6(五)迭代器

本文最后更新于:8 个月前

本文介绍了ES6迭代器模式的概念,Iterator接口的遍历过程,如果通过部署默认Iterator接口让对象变成可遍历的,以及Iterator与for…of循环的关系

ES6(五)迭代器

一、迭代器

迭代器模式包含了两个概念:Iterable可迭代对象Iterator迭代器。前者表示支持迭代操作的数据结构,后者是对于迭代过程的实现。

1、迭代器模式

迭代器模式:通过原生语言结构,让用户无需知道如何迭代就能实现迭代操作。

迭代器模式(特别是在ECMAScript这个语境下)描述了一个方案,即可以把有些结构称为“可迭代对象”(Iterable),因为它们实现了正式的Iterable接口,而且可以通过迭代器Iterator消费。

每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。这种概念上的分离正是Iterable和Iterator的强大之处

2、Iterable 可迭代协议

实现Iterable接口可迭代协议)要求同时具备两种能力:

  • 支持迭代的自我识别能力;
  • 创建实现Iterator接口的对象的能力。

在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的Symbol.iterator作为键。这个默认属性会引用一个迭代器工厂函数,在需要的时候后台会调用这个工厂函数返回一个新的迭代器iterator

检查一个对象是否满足可迭代协议,可以通过查看默认迭代器属性Symbol.iterator

1
2
3
4
let num = 1;
let arr = [1,2,3];
num[Symbol.iterator]; //undefined
arr[Symbol.iterator]; //ƒ values() { [native code] }

3、Iterator 迭代器协议

Iterator迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。不同迭代器的实例之间没有关联。

迭代器API使用next()方法遍历数据,next()方法每次返回一个IteratorResult对象,包含两个属性done和value。done是boolean类型,表示后面是否还有值;value表示返回的值。当遍历完毕后继续调用next(),会一直返回{done:false,value:undefined}

二、Iterator

1. 简介

任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。Iterator 接口主要供for...of消费。

Iterator 的遍历过程是这样的:

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

下面模拟实现一个Iterator接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 主要是要实现next方法,它返回两个信息:value和done
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}

var it = makeIterator(['a', 'b']);
it.next() // {value: 'a', done: false}
it.next() // {value: 'b', done: false}
it.next() // {value: undefined, done: true}

var it1 = makeIterator(['a', 'b']);
for(var value of it1) {console.log(value)} // Uncaught TypeError: it1 is not iterable,因为没有部署Symbol.iterator属性

2. 默认接口

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable),就可以被for..of循环遍历。调用这个接口,就会返回一个编辑器对象。

image-20230329112127305

image-20230329112231441

原生部署了Iterator接口的数据结构有7种:

  • Array
  • Set
  • Map
  • String
  • 函数的arguments对象
  • NodeList对象
  • TypedArray

对象Object默认没有Iterator接口。

1
2
3
4
5
6
var arr = [1,2,3]
var iter = arr[Symbol.iterator]()
iter.next() // {value: 1, done: false}
iter.next() // {value: 2, done: false}
iter.next() // {value: 3, done: false}
iter.next() // {value: undefined, done: true}

一个对象要想能够执行 for..of 循环,就必须在 Symbol.iterator 属性上部署遍历器生成方法(原型链上部署也可以)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class RangeIterator{
constructor(start,end){
this.value = start
this.end = end
}
[Symbol.iterator]() { return this }
next(){
var value = this.value
if(value < this.end){
this.value++
return { value, done:false }
}
return {value: undefined, done: true}
}
}
for(var x of new RangeIterator(3,6)){
console.log(x)
}
// 3 4 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 为对象添加Iterator接口
var obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator](){
const self = this
let index = 0
return {
next(){
if(index < self.data.length){
return { value: self.data[index++], done: false }
}
return { value:undefined, done: true }
}
}
}
}
for(const value of obj){
console.log(value)
}
// hello world

Symbol.iterator()方法的最简单实现,还是使用 Generator 函数

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};

for (let x of obj) {
console.log(x);
}
// "hello"
// "world"

3. 调用Iterator接口的场合

以下场合都会调用默认的Symbol.iterator接口

  1. 解构赋值,数组和Set的解构赋值
  2. 扩展运算符
  3. yield*,后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

三、for…of 循环

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

可以使用for...of循环遍历的数据结构:数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、 Generator 对象,以及字符串。

for…in 循环主要为遍历对象设计,不适合遍历数组。它有以下缺点:

  • 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下,for...in循环会以任意顺序遍历键名。

for…of 循环与 for…in 循环相比,它有着同for...in一样的简洁语法,但是没有for...in那些缺点

for…of 循环与 数组的 forEach 相方法比,它可以与breakcontinuereturn配合使用,但是后者不行。


ES6(五)迭代器
http://timegogo.top/2023/03/29/JavaScript/ES6(五)迭代器/
作者
丘智聪
发布于
2023年3月29日
更新于
2023年7月16日
许可协议