forEach、for ... of、map循环一些区别
1. 循环机制
forEach的循环是基于传统的 for
循环机制实现的,遍历数组中的每个元素的过程是同步操作。
for...of的循环的本质是一个迭代循环, for...of
循环使用的是迭代器协议(Iterator Protocol),在遍历过程中是同步的 这个同步和同步操作不是一个意思,后面会讲到 ,每次调用迭代器的 next()
方法
map 方法是 JavaScript 数组的一个高阶函数,它接受一个回调函数作为参数,并对数组中的每个元素依次执行该回调函数。这个循环过程也是同步的,整个过程会按顺序执行,如果同步操作中包含异步 map()
方法会等待每个异步操作完成后再进行下一步。
2. 可执行的操作
- forEach循环请求异步的时候
前面提到了forEach
的循环机制是for循环,所以他每次循环是不会等待异步请求完成了再进行下一次循环的。这就叫同步操作
javascript
const arr = [1, 2, 3];
arr.forEach(async (item) => {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(item);
});
console.log('done');
//输出结果
done 1 2 3
在这个例子中,我们使用了 async/await
来让异步操作顺序执行。在回调函数中,我们使用了 await
关键字等待 1 秒钟后输出当前元素。
然而,如果你运行这段代码,你会发现你使用了 await
关键字来等待异步操作完成并没有生效,因为 await
无法直接用于 forEach
方法上,forEach
方法不支持返回 Promise
对象,它始终返回 undefined
,也就是说,forEach
方法本身会继续执行下一次循环,而不会等待异步操作的完成,
javascript
下面则是基于上面的解决办法,假设需要用forEach进行循环请求
const arr = [1, 2, 3];
let promises = [];
arr.forEach(async (item) => {
//this.$get()是我自己封装的请求方法,返回的是一个包含请求结果的promise对象
promises.push(this.$get('https://接口地址..?id='+item))
});
let results = await Promise.all(promises);
results.forEach((res, index) => {
rows[index].arr = res.rows;
});
console.log(rows);
// [{ id: 1, arr: [...] }, { id: 2, arr: [...] }, { id: 3, arr: [...] }]
在本例中,我们将所有的异步操作推入 promises
数组中,并使用 Promise.all
方法等待它们的完成。一旦所有异步操作都完成了,我们就可以使用 results
数组中的结果来更新 rows
数组。
在这里forEach就和for循环没任何区别了,forEach像是一个for循环的语法糖
当然或许有比我更好的办法,我这可能有点麻烦。
- for...of循环请求异步的时候
ini
let rows=[1,2,3]
for (let i of rows) {
//this.$get()是我自己封装的请求方法,返回的是一个包含请求结果的promise对象
let res = await this.$get('请求接口地址' + i);
i.arr = res.rows;
}
在 for...of
循环中,使用迭代器协议遍历数组的每个元素,循环体内部的代码是同步执行的,但是可以使用 await
关键字等待异步操作的完成,来保证异步操作按照正确的顺序执行。
在循环体内部使用 await
关键字等待异步操作完成时,for...of
循环本身会被阻塞,直到异步操作完成后才会继续执行下一次循环。 这就是关键所在。
for...of
循环中使用 await
关键字时,JavaScript 引擎会暂停当前的执行上下文,并将控制权交还给调用栈,直到这个 Promise 被 resolve 或 reject。一旦 Promise 被 resolve,程序将恢复执行,并且 for...of
循环将继续进行下一次迭代。
- 用map方法进行异步请求的时候
javascript
const array = [1, 2, 3];
const promises = array.map(async (i) => {
//this.$get()是我自己封装的请求方法,返回的是一个包含请求结果的promise对象
const {data} = await this.$get('接口地址?type=' + i);
console.log(data) //输入出的是请求结果的信息
return data;
});
Promise.all(promises) .then((newArray) => {
// 输出异步操作结果的新数组
console.log(newArray);
// 错误处理
}) .catch((error) => {
console.error(error);
});
这里你会有疑惑吧,data,而返回值却是primise对象
这是因为map
方法是一种并行执行的方式,它会同时启动所有的异步请求。这保证了异步操作按顺序完成,并且返回的新数组与原始数组保持一致的顺序。
map
方法只是会等待这些 Promise 对象完成后再返回结果,是不能在里面处理异步返回的数据的
总结
当使用forEach方法进行循环遍历时,它是一个同步操作,循环内包含异步操作,是不会等待异步返回的结果。
解决这个问题,你可以使用 for...of
循环来遍历数组,并在每次迭代中使用 await
关键字暂停迭代过程,等待异步操作的完成
在 map()
方法中执行异步操作时,它并不会像 forEach()
方法那样继续迭代到下一个元素。map()
方法会等待每个回调函数的异步操作,例如Promise 对象解析完成,并将解析后的值存储在新数组中的相应位置。这保证了异步操作按顺序完成,并且返回的新数组与原始数组保持一致的顺序。
知识拓展
await
关键字用于等待一个异步操作完成,并暂停当前函数的执行,直到异步操作返回结果。它只能在 async
函数内部使用,当遇到 await
关键字时,函数的执行会被暂停,直到异步操作返回结果。这样可以避免回调地狱和复杂的 Promise 链式调用。
迭代器(Iterator)是一种用于遍历数据集合的接口,它提供了一种统一的方式来访问集合中的每个元素,而不暴露其内部的实现细节。迭代器的核心在于它定义了一个 next() 方法,next() 方法的调用是同步的。
迭代器的迭代过程是同步的。在这个过程中,每次调用都会立即返回对应的元素值和遍历状态,不会创建新的宏任务或引入异步操作。