map、forEach、reduce 遇到稀疏数组和回调参数会怎样?
先理解原理和示例,再练习完整口述与追问。
回答要点
回调收到 value/index/array;map 产生新数组,forEach 返回 undefined;空位不等于值 undefined;reduce 初始值决定空数组行为。
原理拆解
map 适合把每个已有元素映射为新值,返回同长度的新数组,原数组中的真正空位通常仍为空位。forEach 用来执行副作用,返回 undefined,不能用返回值收集结果。二者都会向回调传入元素、索引和原数组,因此 ['1','2','3'].map(parseInt) 的结果是 [1, NaN, NaN]:第二个回调参数被 parseInt 当成进制,parseInt('2',1) 非法,parseInt('3',2) 也无法解析。需要写 arr.map(value => parseInt(value,10)) 或使用 Number。
这些数组迭代方法在开始时读取长度,过程中追加到原长度之外的元素不会被本轮访问,尚未访问就删除的索引可能被跳过。它们按属性是否存在决定是否调用回调,所以原型链上存在的索引也会被访问;空位与显式存储 undefined 不同。
reduce 传了初始值就从该值开始,否则取第一个存在的元素作为累积值,再从后面开始遍历。空数组或全为空位的数组,不传初始值会抛 TypeError;显式传 undefined 仍然算提供了初始值。异步回调也要注意:forEach 不会等待 async 回调全部完成,串行使用 for…of 加 await,并行则用 map 生成 Promise 后 Promise.all,必要时再限制并发数。
示例与推演
const source = [, undefined, 2]
let visits = 0
const result = source.map(value => { visits++; return value })
console.log(visits, 0 in result, 1 in result) // 2 false true
console.log(['1', '2', '3'].map(value => parseInt(value, 10))) // [1, 2, 3]
回调次数取决于索引属性是否存在,不是简单等于 length。
易错点
forEach(async fn) 不会把所有回调组合成一个可等待结果。需要并行等待时使用 Promise.all,有限并发还需单独调度。
参考回答可直接练习口述
map 适合把每个已有元素映射为新值,返回同长度的新数组,原数组中的真正空位通常仍为空位。forEach 用来执行副作用,返回 undefined,不能用返回值收集结果。二者都会向回调传入元素、索引和原数组,因此 ['1','2','3'].map(parseInt) 的结果是 [1, NaN, NaN]:第二个回调参数被 parseInt 当成进制,parseInt('2',1) 非法,parseInt('3',2) 也无法解析。需要写 arr.map(value => parseInt(value,10)) 或使用 Number。