场景
for...of....
的原理是?
迭代器模式
看下 维基百科 给的定义:
In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.
说白了就是有个容器类,有一个迭代器类,容器类持有一个迭代器类的对象,然后我们不需要知道容器中元素的具体结构,通过迭代器对象就能够进行遍历。
![image-20220226101825545](https://nakoruru.h7ml.cn/httpproxy/static.5ibug.net/vitepress/assets/images/designPattern/windliangblog.oss-cn-beijing.aliyuncs.comimage-20220226101825545.png)
不妨可以看下 java
的具体实现:
1public interface Iterator<E> {
2 boolean hasNext();
3 void next();
4 E currentItem();
5}
6
7// 迭代器类
8public class ArrayIterator<E> implements Iterator<E> {
9 private int cursor;
10 private ArrayList<E> arrayList;
11
12 public ArrayIterator(ArrayList<E> arrayList) {
13 this.cursor = 0;
14 this.arrayList = arrayList;
15 }
16
17 @Override
18 public boolean hasNext() {
19 return cursor != arrayList.size();
20 }
21
22 @Override
23 public void next() {
24 cursor++;
25 }
26
27 @Override
28 public E currentItem() {
29 if (cursor >= arrayList.size()) {
30 throw new NoSuchElementException();
31 }
32 return arrayList.get(cursor);
33 }
34}
35
36public class Demo {
37 public static void main(String[] args) {
38 ArrayList<String> names = new ArrayList<>();
39 names.add("wind");
40 names.add("liang");
41 names.add("2022");
42
43 Iterator<String> iterator = new ArrayIterator(names);
44 while (iterator.hasNext()) {
45 System.out.println(iterator.currentItem());
46 iterator.next();
47 }
48 }
49}
容器类使用 java
自带的 ArrayList
类,然后我们手动实现一个迭代器类 ArrayIterator
。
js 的迭代器模式
js
中我们不需要专门定义迭代器的类了,我们可以让容器包含一个 Symbol.iterator
方法,该方法返回一个迭代器对象。
迭代器对象包含一个 next
方法用来获取元素,同时获取到的元素除了本身的 value
外,还返回一个布尔型变量代表是否有下一个元素。
1function container(arr) {
2 let nextIndex = 0
3 return {
4 [Symbol.iterator]() {
5 return {
6 next() {
7 return nextIndex < arr.length
8 ? {
9 value: arr[nextIndex++],
10 done: false,
11 }
12 : {
13 value: undefined,
14 done: true,
15 }
16 },
17 }
18 },
19 }
20}
21
22const list = container(['wind', 'liang', '亮'])
23const iterator = list[Symbol.iterator]()
24
25while (true) {
26 const data = iterator.next()
27 if (data.done)
28 break
29 else
30 console.log(data.value)
31}
事实上,数组已经为我们提前实现了迭代器,我们直接通过 Symbol.iterator
方法拿到,不需要自己再实现了。
1const array = ['wind', 'liang', '亮']
2const iteratorArray = array[Symbol.iterator]()
3
4while (true) {
5 const data = iteratorArray.next()
6 if (data.done)
7 break
8 else
9 console.log(data.value)
10}
还有字符串也为我们内置了迭代器。
1const string = 'windliang'
2const iteratorString = string[Symbol.iterator]()
3
4while (true) {
5 const data = iteratorString.next()
6 if (data.done)
7 break
8 else
9 console.log(data.value)
10}
同理,Map
、Set
都帮我们内置了 Symbol.iterator
方法,可以返回一个迭代器。
此外,我们也不需要每次都去 while
循环、然后判断是否结束循环了,直接使用 for...of...
即可。
1const array = ['wind', 'liang', '亮']
2for (const a of array)
3 console.log(a)
4
5const string = 'windliang'
6for (const s of string)
7 console.log(s)
注意
因为数组是通过 index
来获取元素的,如果在遍历过程中删除元素,可能会产生非预期内的事情。
1const array = ['wind', 'liang', '亮']
2for (const a of array) {
3 console.log(a)
4 if (a === 'wind')
5 array.splice(0, 1)
6}
7console.log(array)
可以先思考下会怎么输出,然后看下结果:
我们是成功删除了 wind
,但是原数组中 liang
就不会遍历到了,也比较好理解。
开始的时候,指针 index
指向 wind
,进行了输出 console.log(a); // wind
10 1 2
2wind liang 亮
3 ^
4index
此时删除了 wind
,array.splice(0, 1);
数组整体前移。
然后指针后移,遍历下个元素。
10 1
2liang 亮
3 ^
4 index
就直接走到 亮
了,而没有遍历 liang
。
原因就是 liang
的位置之前是 wind
,wind
之前已经遍历过了,指针后移就把 liang
跳过了。
总
迭代器模式的好处就是可以不知道容器中元素的结构就可以遍历,一般由容器提供一个迭代器供我们使用。为了实现不同的遍历顺序,只需要提供新的迭代器即可。
一般编程语言中都内置了迭代器,js
也不例外,在 Array
、String
、Map
、Set
中都内置了Symbol.iterator
方法返回一个迭代器对象,同时提供了for...of...
语法统一了各个对象的遍历方式。