1.生成器函数和生成器
生成器函数是可以返回一个可迭代对象的特殊函数,
生成器是一个特殊的迭代器,
在js中可以使用function*来定义一个非连续执行的函数作为迭代算法,
function* name() {
yield value;
yield value;
yield value;
}
name: 函数名
value:迭代的值
- function*:定义一个生成器函数
- yield:定义迭代对象的值
最初调用生成器函数时,生成器函数不执行任何代码,而是返回一种称为生成器的特殊迭代器。通过调用 next()
方法消耗该生成器时,生成器函数将执行,直至遇到 yield
关键字。
迭代遍历一个生成器
// 生成器函数
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
for (let i = 4; i < 10; i++) {
yield i;
}
}
// 生成器
let iterator = generateNumbers();
while (true) {
let { value, done } = iterator.next();
if (done) {
break;
}
console.log(value);
}
console.log(iterator);
成功将生成器中yield的值全部迭代遍历出来,这是使用了迭代器的next()方法,而生成器是一个特殊的迭代器,
2.迭代器
在 JavaScript 中,迭代器(iterator)是一个对象,它拥有一个next方法,next方法会返回一个对象,对象中包括了两个属性:done,value
- done: 当迭代器对象中没有值可以跌打时为true,否则为false
- value: 当前迭代的值,
迭代器通过调用next方法向后读取值,并返回到value属性中,这样就实现了迭代算法逻辑
迭代器的特性
从结果上看,迭代器和数组的作用差不多,都能遍历一串数据,但是和数组不同,
迭代器往往是不知道长度的,迭代遍历时,只有done属性为true时才会停止,
数组是已知长度的,当一个数组被引用时,内存中就需要划分出足够的空间来存放,
而迭代器只需要提供当前值的空间即可,所以一个数组时有限长度的,而一个迭代器的值可以取0 和 infinity(无穷大) 之间的整数范围
3.可迭代对象
在实际的运用中,可迭代对象发挥了迭代器的特性,可以用来遍历一组数据,这组数据可以非常巨大(长度),使用迭代器则可以很轻松的慢慢读出数据(用实际换空间),
在js中,可迭代对象都拥有一个[Symbol.iterator]属性,它是一个函数,返回一个迭代器,迭代器中包括了可迭代对象的所有值,
常见的可迭代对象
包括了:数组,集合,映射,弱集合,弱映射,字符串 以及其他一些类数组对象(domlist,argument...)
// 可迭代的对象 数组,集合,映射,弱集合,弱映射,字符串 以及其他类数组对象
let arr = new Array(1, 2, 3, 4, 5);
let set = new Set([1, 2, 3, 2, 1, 2]);
let map = new Map([['name', 'John'], [{ type: 'obj' }, 'object']])
let wSet = new WeakSet([{ name: 'John' }, { age: 30 }]);
let wMap = new WeakMap([[{ name: 'John' }, 'person'], [{ age: 30 }, 'person']])
let str = new String('Hello World')
let div = document.querySelectorAll('div');
function fn() {
return arguments;
}
// 它们原型对象上都存在[symbol.iterator]()方法, arr[Symbol.iterator]().next()
fn()[Symbol.iterator]().next().value
它们原型对象上都存在[symbol.iterator]()方法, 例如,arr[Symbol.iterator]().next(),
[symbol.iterator]是一个Symbol值,关于Symbol可以参考:js基本类型---symbol标识符_jis中symbol是什么意思-CSDN博客
symbol类型是独一无二的,使用symbol作为原型属性,可以防止属性被覆盖修改
注意:普通对象不是可迭代对象,它没有[symbol.iterator]()方法
扩展运算符(...)和for of
扩展运算符可以展开一个数组,并把值全部返回出来,
for of可以遍历数组的值
这些都是基于可迭代对象的[symbol.iterator]()方法,也就是说,其他可迭代对象也可以使用扩展运算符和for of,这也是普通对象不能使用for of的原因,普通对象不是一个可迭代对象
console.log(...arr)
console.log(...set)
console.log(...map)
console.log(...fn(1,2))
for(let value of arr){
console.log(value);
}
for(let value of set){
console.log(value);
}
for(let value of map){
console.log(value);
}
for(let value of fn(1,2)){
console.log(value);
}
重写[symbol.iterator]()方法
既然判断一个可迭代对象的方式是看这个对象有没有[symbol.iterator]()方法,那么可以给普通对象加上一个[symbol.iterator]()方法,使其成为一个可迭代对象
let obj = {
value: 0,
max: 10,
[Symbol.iterator]() {
return {
max: this.max,
value: this.value,
next() {
if (this.value == this.max) {
return {
value: undefined,
done: true
}
} else {
return {
value: this.value++,
done: false
}
}
}
}
}
}
根据[symbol.iterator]()的逻辑,它会返回一个对象,这个对象有next方法,next方法会返回一个包含value和done属性的对象,
那么这个obj对象就相当于一个存放了0到9的一个可迭代对象
for(let value of obj){
console.log(value);
}
console.log(...obj);
那么这样就可以对它执行for of 遍历和扩展运算
所以扩展运算符(...)和for of都是基于[symbol.iterator]()方法实现的,