什么是迭代
- 循环遍历一个有限集合
- 按照预定的顺序遍历
- 遍历的过程与具体的结构无关
- 有明确的的开始和结束标识
我们来看一个例子
const array = [1,2,3,4]
for(const i = 0; i < array.length; i++ ){
console.log(array[i]);
}
这个代码遍历了一个数组,并相继输出每个元素。数组是最典型的迭代结构。在迭代的过程,顺序是确定的,数量是有限的,但是遍历之前要确定数据结构,才会开始遍历。并且访问数据的方式是与数组高度相关的--通过下标来获取元素,也没有明确的开始和结束的标识
在Java,Pathon,C++中,对迭代模式已经有了完备的支持。JavaScript在ES6之后,也支持了迭代模式
来看几个迭代的例子
for-in
const array = [1,2,3,4];
for(const item of array){
console.log(item);
}
// 1
// 2
// 3
// 4
const set = new Set().add(1).add(2).add(3);
for(const item of set){
console.log(item);
}
//1
//2
//3
for(const item of '1234'){
console.log(item);
}
//1
//2
//3
//4
数组、set、字符串都是可迭代结构,而for-of语法就是用来迭代可迭代结构的,所以就可以看到上面的输出
Array.from
const array = Array.from('123');
console.log(array);
//[ '1', '2', '3' ]
Array.form()接收一个可迭代结构作为参数,然后用迭代结构中的每个元素生成新的数组
Array.from()除了接受可迭代结构作为参数,还可以接受含有length属性和可索引元素的结构,不过这个不是迭代器讨论的范畴,这里跳过
new Set()
const set = new Set('123');
console.log(set);
//Set(3) { '1', '2', '3' }
const set2 = new Set(['a', 'b', 'c']);
console.log(set2);
//Set(3) { 'a', 'b', 'c' }
new Set()也接收一个可迭代结构,用于生成新的set对象。
set本身也是一个的可迭代结构
new Map()
const map = new Map([[1, 2], [12, 23]]);
console.log(map);
// Map(2) { 1 => 2, 12 => 23 }
const set = new Set().add(['a', 'b']).add(['c', 'd']);
const map2 = new Map(set);
console.log(map2);
// Map(2) { 'a' => 'b', 'c' => 'd' }
new Map()也接收一个可迭代结构作为参数,由于map结构的特殊性,要求迭代结构的每个元素需要是[key value]样的数组。但丝毫不影响它接收一个可迭代结构的本质性。在第二个例子中,传入了一个可迭代结构set,set的每个元素都是[key value],依旧可以生成map
map也是一个可迭代结构
OK,上面就是一些与迭代器有关的基本应用,看起来花里五哨的,是不是。
别怕,下面就深入迭代器了解一下
迭代器协议
迭代器返回的结构
{
done: boolean;
value: any
}
迭代器每次迭代的结果结构必须是上面的结构,包含done和value两个属性。
其中的done表示是否迭代结束,如果done === false,即没有迭代结束;如果done === true,即迭代结束。其中的value,是每次迭代返回的真正的值。
假设我们要遍历[1, 2, 3]数组,迭代器返回的值分别是:
// first
{
done: false;
value: 1;
}
// second
{
done: false;
value: 2;
}
//third
{
done: false;
value: 3;
}
//fourth
{
done: true;
value: undefined;
}
看到这里,你可以会有疑问,为啥数组迭代出来的是这个东西,和
for-of输出结果完全不一样。
迭代器
讲到这里,就不得不提迭代器的概念了。
可迭代结构又称可迭代对象,我们要想迭代可迭代对象,就必须要使用迭代器。而这个迭代器可以从迭代对象中获取。在可迭代对象中,有个属性叫Symbol.iterator,这个属性指向一个函数,调用这个函数就会得到对应的迭代器。
迭代器的使用
迭代的过程,相当于有一个游标指着迭代对象中的元素,当迭代器读取完一个元素之后,这个游标就会指向下一个元素,直至遍历完成。
我们看代码
const array = [1, 2, 3];
const iteratorArray = array[Symbol.iterator]();
let flag = null;
do {
console.log(flag = iteratorArray.next());
} while (flag.done === false)
console.log(iteratorArray.next());
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }
// { value: undefined, done: true }
- 首先,通过调用
Symbol.iterator所指向的函数,我们获取到了数组的迭代器。之后通过调用迭代器的next()函数,来获取迭代对象中的元素,并且游标指向下一个。 - 这个过程中done的值为false;如果迭代完成,done会变成ture。其中value的值,就是迭代的元素的值。
- 迭代结束之后,还可以调用next,只不过没有了意义
读者:好吧,我明白迭代器怎么用了,但为啥和
for-of输出不一样呢作者:
for-of结构会在后台调用可迭代对象提供的产生迭代器的函数,即Symbol.iterator。从而创建一个迭代器实现迭代。也就是说我们实际写代码的过程中,不需要显示调用这个工厂函数来生成迭代器,也不需要我们手动处理看起来怪怪的结构了--{done,value}
类似for-of的原生语言结构
像 for-of的原生语言结构有好多:
- 数组解构。没错,解构的对象没必要是数组,是个可迭代对象就好了
- 扩展操作符。这个和上面一样,数据来源是个可迭代对象就可以。
- Array.from(), new Set(), new Map(),这些函数都会接收一个可迭代对象作为参数,生成对应的实例化对象
- Promise.all(), Promsie.race()。这里也接收一个可迭代对象作为参数
不知道你有没有反应过来,这个迭代器的返回结果数据结构,和生产器(generator)返回的数据结构一模一样
下面对set对象做数组解构和扩展操作符的计算,其他的读者可以自己尝试
const set = new Set().add(['a', 'b']).add(['c', 'd']);
console.log(set);
// Set(2) { [ 'a', 'b' ], [ 'c', 'd' ] }
//对set做扩展操作符运算
const array = [...set];
console.log(array);
// [ [ 'a', 'b' ], [ 'c', 'd' ] ]
// 对set做数组解构运算
const [a, b] = set;
console.log(a);
console.log(b);
// [ 'a', 'b' ]
// [ 'c', 'd' ]
可以很容易猜到,数组解构和扩展操作符两种计算,都是先把迭代对象迭代一遍,获取每个元素,之后再生成对应的内容
哪些结构是可迭代的
- 字符串
- 数组
- map
- set
- arguments对象
- NodeList等DOM集合类型
是不是比你以为的还要少一些😁
如何看一个结构是否为可迭代结构
- 用for-of遍历一下,如果可以遍历,那就是可迭代结构
- 看该对象的
Symbol.iterator的属性是否为undefined
下一篇文章讲自定义迭代器: JS讲透迭代器-自定义篇
总结
- 什么是迭代
- 举了几个JS中运用迭代器的例子
- 讲述了迭代器协议
- 有不明白的,留言告诉我