迭代与循环
说到迭代,大家不可避免的就会想起循环,菜鸟博主起初也是迭代和循环傻傻分不清楚(虽然现在也不是很清楚),这里来解释下这两者的区别。
表示“重复”这个含义的词有很多, 比如 循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate).
循环算是最基础的概念, 凡是重复执行一段代码, 都可以称之为循环. 大部分的递归, 遍历, 迭代, 都是循环。所以说,我认为迭代可以算作是循环的子集。
下面是百度百科给出的迭代的定义
迭代是指让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值
所以说:
这个过程当中, 旧值和新的值可以不连续, 可以不是顺序的,只要满足以下下三个条件的都算是迭代
1 有个初始的值
2 有一套算法,对这个初始值操作, 并计算出新的值
3 新的值还可以再次调用刚才的算法,再产生新的值,这个过程是可控的
下面给出一个例子大家就了解了
for(i = 0;i < 5;i++){ //循环
alert("haha");
}
for(i = 0;i < 5;i++){ //递增式迭代
sum = sum + i;
}
复制代码
JavaScript迭代器的由来
我认为一个知识点,首先我们要了解这个知识点当初是被发明出来解决什么问题的。
下面这个栗子0.0用到了迭代
let collection = ['ming', 'li', 'zhang'];
for (let index = 0; index < 3; ++index) {
console.log(collection[index]);
}
复制代码
通过这个方式来进行迭代并不是最理想的。
对于上面这个例子:
我们需要知道怎么获取我们想要的数据,以这个例子来说,我们先是通过引用获得数据对象然后再通过[]操作符来获取对象的属性值,这种方式并不适合所有的数据结构。
例如:我有一个数据集合[1,2,3,4,5] 而我用[1-5]来表示,这时候用上面的方法就不行了
也就是说,这种迭代方法并不通用。
于是ES5新增了Array.prototype.forEach()方法,向通用迭代迈进了一步。
let arr = [1, 2, 3, 4];
function square(num) {
console.log(num, num * num);
}
arr.forEach(square);//输出: 1 1,2 4, 3 9, 4 16
复制代码
但是这个方法还是不够用有局限性,它包括但不限于只能用于数组的缺点。
于是JavaScript在ES6支持了迭代器模式,也就是我们今天的主角。
迭代器
迭代器模式
下面我用一种通俗的语言来描述一下迭代器模式
迭代器模式是一个方案,这个方案告诉我们:
当我们需要执行迭代时,可以这样子做,即可迭代对象(iterable) 的 迭代器工厂函数会创建一个迭代器(iterator),这个迭代器会在每一次的循环中返回我们需要的数据。
这可能很难理解,朋友们且听菜鸟博主慢慢道来。
先简单说一下可迭代对象,我们见到的数组、字符串、map、set等等都是可迭代对象,同时它不一定是集合对象,它也可以是用用和数组相似行为的其它的数据结构。比如我上面举的“例如”的例子。
进入正题
我们直接来自己创建一个可迭代对象,这样你也许就可以理解迭代器,迭代器工厂函数和可迭代对象了
这里有一个range对象,他代表一个数字区间,我现在想通过for..of来输出他的所有数字,但他现在并不是一个可迭代对象无法被for…of接收
let range = {
from: 1,
to: 5
};
复制代码
为了让 range 对象可迭代(也就让 for..of 可以运行)我们需要为对象添加一个名为 Symbol.iterator 的方法(一个专门用于使对象可迭代的内置 symbol)。也即是我说的迭代器工厂函数
let range = {
from: 1,
to: 5
};
// 1. for..of 先是调用这个迭代器工厂函数:
range[Symbol.iterator] = function() {
// ……迭代器工厂函数会返回一个迭代器对象也就是迭代器(iterator object):
// 2. 接下来,for..of 仅与此迭代器一起工作,在每一轮循环中从迭代器中获取新的数据
return //这个return后面就是迭代器了{
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
//这个对象里的value就是for..of需要的数据
//而当返回的对象的done属性为true时,这个循环就停止了
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 现在它可以运行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
复制代码
下面我来对迭代器,迭代器工厂函数和可迭代对象逐个进行解释
迭代器
这里我重新说一遍迭代器是什么
通过迭代器工厂函数返回的一个带有next函数的对象,并且这个next在循环中每次调用会返回value和done属性的值,通过done来判断迭代是否停止,value就是我们需要的数据,这样的一个对象,就是迭代器。
总结:
迭代器就是一个有next()方法的对象。
下面我通过简单的数组来演示next函数,工厂函数,以及迭代器。
let arr = ['ming', 'li'];
//迭代器工厂函数
console.log(arr[Symbol.iterator]); //ƒ values() { [native code] }
let iter = arr[Symbol.iterator]();//这里item就是构造器了
console.log(iter);//Array Iterator {}
//下面执行迭代
console.log(iter。next());//{value: "ming", done: false}
console.log(iter。next());//{value: "li", done: false}
console.log(iter。next());//{value: undefined, done: true}
复制代码
可迭代对象
在上面我已经简单的说过可迭代对象是什么了,这里我给出他的定义
若一个对象拥有迭代行为,比如在 for…of 中会循环哪些值,那么那个对象便是一个可迭代对象。
内置可迭代对象
String、Array、TypedArray、Map 和 Set 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法。也就是说我们并不需要为这些对象去像我上面的例子一样创建迭代器工厂函数。
自定义可迭代对象
就像我们的例子一样,我们可以自己定义可迭代对象
可迭代对象的原生语言特性
包括但不限于:
for-of
数组结构
Array.from
创建集合(map)
创建映射(set)
复制代码
这些原生语言结构会自动调用可迭代对象自带的迭代器工厂函数来创建迭代器。
迭代器工厂函数
它就是一个名为 Symbol.iterator 的方法(一个专门用于使对象可迭代的内置 symbol)
- 当 for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。
- 这个方法必须返回一个 迭代器(iterator) —— 一个有next 方法的对象。 从此开始,for..of 仅适用于这个被返回的对象。 当 for..of 循环希望取得下一个数值,它就调用这个对象的next() 方法。
- next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当done=true 时,表示迭代结束,否则 value 是下一个值。
参考资料,MDN, 现代JavaScript教程。
如有错误,请在评论区告诉我,我会及时改正