迭代器_可迭代对象

迭代器(iterator)

简单介绍

  1. 迭代器是一个对象,它可以帮助我们遍历某种容器对象(例如数组,map,set,字符串),即:迭代器是能够帮助我们对某个数据结构进行遍历的对象)
  2. 迭代器并不是js特有;java,python也有迭代器,实现方式不同

js中的迭代器

  1. 在js中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)

  2. 迭代器协议定义产生一系列值(不管是有限个还是无限个)的标准方式;在js中,这个标准就是一个”特定”的next方法:

    • next方法要求:

      • 1.一个无参或者有一个参数的函数,

      • 2.返回一个对象, 这个对象包含两个属性

        • done:当所有想要遍历的对象已经遍历完了,则为true,否则为false

        • value:当前遍历到的值,如果done为true,则value可忽略(undefined)

          ps:建议都写上,提高代码可读性

自定义迭代器

可遍历数组的迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const arr = [1, 2, 3];
// 创建一个可以遍历数组的迭代器
const createIterator = (arr) => {
let index = 0
const iterator = {
next() {
if (index < arr.length) {
return {
value: arr[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
return iterator;
}

let iterator = createIterator(arr);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

一个无限迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createIterator() {
let index = 0;
return {
next() {
return {
value: index++,
done: false
}
}
}
}
let a = createIterator()
console.log(a.next());
console.log(a.next());
console.log(a.next());
// ...可无限调用next方法

可迭代对象(iterable)

在实现自定义迭代器的代码中,相关变量的 关联性较强;我们将以上代码再次抽取封装,让其变成一个可迭代对象.

简单介绍

什么是可迭代对象?

他与迭代器是不同的概念

  1. 当它实现了iterable protocol协议,它就是一个可迭代对象
  2. 对这个对象的要求是必须实现@@itaretor,在代码中可使用[Symbol.iterator]来访问该方法
  3. 而[Symbol.iterator]要求返回一个迭代器(对象)

可迭代对象的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const iterableObj = {
values: [1, 2, 3],
[Symbol.iterator]: function () {
let index = 0;
const iterator = {
// 注意此处必须是箭头函数,否则无法找到names属性
next: () => {
if (index < this.values.length) {
return {
value: this.values[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
return iterator
}
}

let iterator = iterableObj[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// ...
// 生成新的迭代器,不管之前的迭代器遍历到何种程度,新的迭代器会重新遍历.
iterator = iterableObj[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

for…of

原理

for…of遍历的对象必须是一个可迭代对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log(Object.getOwnPropertySymbols(Array.prototype));	// [].__proto__
// [Symbol(Symbol.iterator), Symbol(Symbol.unscopables)]
console.log(Object.getOwnPropertySymbols(Map.prototype)); // new Map().__proto__
// [Symbol(Symbol.toStringTag), Symbol(Symbol.iterator)]
console.log(Object.getOwnPropertySymbols(Set.prototype)); // new Set().__proto__
// [Symbol(Symbol.toStringTag), Symbol(Symbol.iterator)]
console.log(Object.getOwnPropertySymbols(String.prototype));// ''.__proto__
// [ Symbol(Symbol.iterator) ]

// 或
console.log(Array.prototype[Symbol.iterator]); // [].__protp__[Symbol.iterator]
// [Function: values]
console.log(Map.prototype[Symbol.iterator]); // new Map().__proto__[Symbol.iterator]
// [Function: entries]
console.log(Set.prototype[Symbol.iterator]); // new set().__proto__[Symbol.iterator]
// [Function: values]
console.log(String.prototype[Symbol.iterator]); // ''.__proto__[Symbol.iterator]
// [Function: [Symbol.iterator]]

可见,Array,MapSetString均实现了[Symbol(Symbol.iterator)]这个方法

类似的,arguments,NodeList集合也都是可迭代对象(实现了[Symbol(Symbol.iterator)]方法)

1
2
3
4
5
function foo() {
console.log(Object.getOwnPropertySymbols(arguments));
}
foo()
// [ Symbol(Symbol.iterator) ]

对象{}因为没有实现[Symbol(Symbol.iterator)]这个方法,所以不能使用for…of循环

1
2
console.log(Object.getOwnPropertySymbols(Object.prototype));
// []

示例

原生数据结构使用for…of遍历

1
2
3
4
5
6
7
8
9
10
let arr = [1, 2, 3]
let set = new Set(arr);
let map = new Map();
map.set(1, 1);
for (let item of map) {
console.log(item);
}
// 数组返回值
// set返回值
// map返回数组形式的键值,形如[key, value]

自定义可迭代对象使用for…of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 使用for...of遍历iterableObj(可迭代对象的实现板块,可直接复制)

for (let item of iterableObj) {
console.log(item);
}
// 1
// 2
// 3
// 将iterableObj的next的if条件改为index < 1,再次使用for...of
// 1

// 我们也可以拿到js原生数据结构的某些可迭代对象的迭代器
let iterator = [1, 2, 3][Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

function foo() {
let iterator = arguments[Symbol.iterator]();
let next = iterator.next();
while (!next.done) {
console.log(next.value, 'next');
next = iterator.next();
}
}
foo(1, 2, 3)
// map,set,string小伙伴可自行测试

在迭代器对象的next方法返回的对象的done为false时,js会将value赋值给for…of循环的item

这就是for…of循环的本质

可迭代对象的应用

for…of

展开语法(…)

1
2
3
4
5
6
console.log(...iterableObj);
// 1 2 3
console.log(...new Set([1, 2, 3]));
// 1 2 3
console.log(...new Map([[1, 1], [2, 2], [3, 3]]));
// [ 1, 1 ] [ 2, 2 ] [ 3, 3 ]

yield*

见生成器模块

解构赋值

1
2
3
4
const names = ['hrm', 'djw', 'hrc']
const [value1, value2] = names;
console.log(value1, value2);
// 解构赋值的原理也是迭代器,是分别将当前迭代器的next().value赋值给value1,value2

一些方法的调用

在创建一些对象时,其构造函数的参数可传入一个可迭代对象

(可参看Map,Set构造函数的ts文件类型声明)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// set的定义
// 构造函数传入一维数组
interface SetConstructor {
new <T = any>(values?: readonly T[] | null): Set<T>;
readonly prototype: Set<any>;
}
// map的定义
// 构造函数传入一个二维数组
interface MapConstructor {
new(): Map<any, any>;
new<K, V>(entries?: readonly (readonly [K, V])[] | null): Map<K, V>;
readonly prototype: Map<any, any>;
}
// ...
console.log(...new Set([1, 2, 3]));
console.log(...new Map([[1, 1],[2, 2]]));

除此之外,还有例如Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable)等方法

自定义类的可迭代性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 新建教室类,该教室的学生可遍历
class Room {
constructor(address, name) {
this.address = address;
this.name = name;
this.students = [];
}
pushStudent(student) {
this.students.push(student);
}
// Room.prototype[Symbol.iterator] = function() {}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.students.length) {
return {
done: false,
value: this.students[index++]
}
} else {
return {
done: true,
value: undefined
}
}
},
// return是对迭代器提前终止的监听,返回值和next一致
// 提前终止包括:break;continue;return;throw
// 另一种情况,在解构的情况下,没有解构所有的值
return: () => {
console.log('迭代器提前终止');
return {
done: true,
value: undefined
}
}
}
}
}

let room = new Room('行远楼A105', '软件工程教室');
room.pushStudent('hrm');
room.pushStudent('xcl');
room.pushStudent('dmy');
room.pushStudent('gq');

for (const student of room) {
console.log(student);
if (student === 'dmy') {
// throw new Error('dmy');
break;
}
}

for (const student of room) {
console.log(student);
}

其他补充

展开运算符…

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
name: 'hrm',
age: '18',
friends: { name: 'djw', age: 18 },
}

let info = { ...obj }

info.friends.name = null;
console.log(info.friends);
console.log(obj.friends);
// { name: null, age: 18 }
// { name: null, age: 18 }

说明:这是ES9引入的新语法,是对”…”展开运算符功能的拓展,和迭代器没有关系;而且,在对象内部使用…展开运算符,是对对象属性的浅拷贝.

想要对对象实现遍历,我们可以换一种思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
name: 'hrm',
age: '18',
friends: { name: 'djw', age: 18 },
}
for (const entry of Object.entries(obj)) {
console.log(entry)
// entry ==> [key, value]
}

// 对象的解构赋值也不同于数组的解构赋值,也是ES9引入的新语法,也与迭代器无关
const { name, age } = obj;
console.log(name, age);