​Map, Filter和Reduce-动画版

本文深入解析Map, Filter和Reduce函数在编程中的应用,通过可视化方式解释如何使用这些高阶函数处理数组,包括数值映射、条件过滤及累加求和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Map, Filter和Reduce已经存在很长时间了。 它们通常被视为函数式编程风格的一部分。

下面的动画看起来可能有点不自然。但是因为只用了创建的俄罗斯方块网格(在以前的《如何制作俄罗斯方块》教程中)。大家可以也来看看。

我们经常把复杂的概念通过形象化的方式记在脑海中。使用可视化作为学习起点的编程文章并不多见。然而,视觉化在教育中是很重要的一环。编程也一样。

即使在使用map、filter和reduce多年之后,仍然常常问自己:是否创建了原始数组的副本?是否修改了对原始数组的引用?写下这篇教程也是为了给这些问题一个答案。

希望是,一旦它被视觉化,它将更容易被记住。

Array.map-将所有值映射到表达式

Array.map(): 将“数值 + 1” 运用到一个有7个数的集合[1, 2, 3, 4, 5, 6, 7]中

1]表达式:数值+1应用于原始数组中的每个项。

2].map()返回一个数组的修改过的副本,使原始数组保持原样。

3]结果:[2,3,4,5,6,7,8](创建原始数组的副本。)

Array.filter–保留所有符合条件的内容


注意:动画中有一个小错误。它应该返回[6,7],而不是[6,7,8]。我马上就改…

1]函数:数值>5应用于原始数组中的每个项。

2].filter()返回数组的修改过的副本-原始副本仍然可用!

3]结果:[6,7,8](仅限通过条件的值。)

Array.reduce–将所有项目减少到一个值
Reducer的一个常见用法是将购物车中所有商品的价格加起来。

Reduce的独特之处在于它使用了一个累计器。

我们需要为累计器设置起始值。这里我们设置的是0。

1]Reducer函数F将会取值然后累计。

在我们的例子中,.reduce(v, a) 将返回所有值的和。

3]结果:28(原数组中所有数字之和)

结论
当然,这些高阶函数也可以(也应该)用于解决各种各样的问题。

Map, Filter和Reduce也可以用于对象数组。它们不限于仅仅处理数值。

今天的分享就到这里,希望本文能帮助到您!

❤️ 看之后

  • 点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
  • 关注公众号「新前端社区」,号享受文章首发体验!每周重点攻克一个前端技术难点。

// 初始化标记点(按sort排序,区分起点终点) function initRouteAndAnimation() { if (points.length < 2) { console.warn('至少需要2个点位生成路线'); if (points.length === 1) createPointMarker(points[0], 0, 1); return; } // 1. 排序并过滤有效点位(确保顺序正确) const sortedPoints = [...points] .filter(p => p.lng !== undefined && p.lat !== undefined) .sort((a, b) => (a.sort || 0) - (b.sort || 0)); // 按sort字段排序 const total = sortedPoints.length; // console.log('排序后点位(含途经点):', sortedPoints.map(p => p.title)); // 2. 绘制所有点位标记(起点、途经点、终点) sortedPoints.forEach((p, i) => createPointMarker(p, i, total, sortedPoints)); // 3. 按高德官方方法配置导航路线(含途经点) AMap.plugin('AMap.Driving', () => { // 初始化驾车导航实例 const driving = new AMap.Driving({ map: map, // 路线直接显示在地图上 panel: null, // 不显示导航面板(如需显示可指定DOM元素) policy: AMap.DrivingPolicy.LEAST_TIME, // 最快路线策略 strokeColor: "#32CD32", // 路线颜色 strokeWeight: 4 // 路线宽度 }); // 4. 定义起点、终点途经点(严格遵循官方格式) const start = new AMap.LngLat(sortedPoints[0].lng, sortedPoints[0].lat); // 起点 const end = new AMap.LngLat(sortedPoints[total - 1].lng, sortedPoints[total - 1].lat); // 终点 const waypoints = []; // 途经点数组 // 提取中间点作为途经点(从第2个到倒数第2个) for (let i = 1; i < total - 1; i++) { waypoints.push(new AMap.LngLat(sortedPoints[i].lng, sortedPoints[i].lat)); } // console.log('官方方法途经点数量:', waypoints.length); // 5. 调用官方search方法规划路线(含途经点) // 官方格式:driving.search(起点, 终点, {waypoints: 途经点数组}, 回调) driving.search( start, end, { waypoints: waypoints }, // 官方要求的途经点配置格式 (status, result) => { // console.log('官方路线规划状态:', status); if (status === 'complete' && result.routes && result.routes.length > 0) { // 规划成功:使用官方返回的路线启动动画 // console.log('官方路线包含的步骤数:', result.routes[0].steps.length); initCarAnimation(result.routes[0]); } else { // 规划失败:降级为直线连接所有点 // console.error('官方路线规划失败,使用备用路线'); const backupPath = sortedPoints.map(p => new AMap.LngLat(p.lng, p.lat)); initCarAnimation({ distance: calculateDistance(backupPath.map(ll => [ll.lng, ll.lat])), steps: [{ path: backupPath }] }); } } ); }); } // 初始化行驶动画(严格沿官方路线移动) function initCarAnimation(route) { console.log('开始初始化行驶动画'); if (carMarker) carMarker.remove(); // 1. 提取原始路径点(来自高德路线规划) const path = []; try { route.steps.forEach(step => { if (step.path && step.path.length > 0) { step.path.forEach(coord => { const lnglat = coord instanceof AMap.LngLat ? coord : new AMap.LngLat(coord.lng, coord.lat); path.push(lnglat); }); } }); } catch (err) { console.error('提取路径点失败:', err); return; } if (path.length < 2) { console.error('路径点不足(至少需要2个),无法执行动画'); return; } // 2. 优化路径点插值(更密集,解决抖动) // 2. 优化路径点插值(严格控制总数,确保15秒匀速) const interpolatedPath = []; const maxTotalPoints = 300; // 最大总点数(平衡平滑与速度) const totalDistance = path.reduce((sum, p, i) => { if (i > 0) sum += path[i - 1].distance(p); return sum; }, 0); // 计算路线总长度(米) const pointsPerMeter = maxTotalPoints / totalDistance; // 每米点数(动态调整) for (let i = 0; i < path.length - 1; i++) { const p1 = path[i]; const p2 = path[i + 1]; const distance = p1.distance(p2); // 按总长度分配点数,避免过多 const segmentPoints = Math.max(2, Math.min(500, Math.ceil(distance * pointsPerMeter))); for (let s = 0; s < segmentPoints; s++) { const ratio = s / (segmentPoints - 1); const lng = p1.lng + (p2.lng - p1.lng) * ratio; const lat = p1.lat + (p2.lat - p1.lat) * ratio; interpolatedPath.push(new AMap.LngLat(lng, lat)); } } // 确保总点数不超过最大值(强制截断,避免速度异常) const finalPath = interpolatedPath .slice(0, maxTotalPoints) .filter(lnglat => { // 过滤经纬度无效的点(如NaN、超出合理范围) return !isNaN(lnglat.lng) && !isNaN(lnglat.lat) && lnglat.lng > -180 && lnglat.lng < 180 && lnglat.lat > -90 && lnglat.lat < 90; }); // 若过滤后路径点不足,补充默认路径(避免动画无法启动) if (finalPath.length < 2) { console.error('有效路径点不足,使用原始路径'); finalPath = path.slice(0, 2); // 至少保留2个点 } console.log(`路径点优化:总长度${Math.round(totalDistance)}米 → 最终${finalPath.length}个点(15秒走完)`); // 4. 配置行驶图标(确保正向无旋转) const carIcon = new AMap.Icon({ image: ICONS.car || './img/student.png', size: new AMap.Size(87, 96), // 与图片实际尺寸一致 imageSize: new AMap.Size(87, 96) }); // 5. 创建车辆标记(锚点精准对齐) carMarker = new AMap.Marker({ position: finalPath[0], icon: carIcon, offset: new AMap.Pixel(-44, -48), // 70/2=35,90/2=45(中心对齐) zIndex: 1000, map: map, rotation: 0 // 强制初始角度为0 }); // 6. 将startAnimation放入内部,确保访问所有变量 function startAnimation() { const duration = 15000; // 固定15秒 console.log(`动画启动:总时长${duration/1000}秒,路径点总数${finalPath.length}个`); if (typeof carMarker.moveAlong === 'function') { // 执行动画(禁用自动旋转) carMarker.moveAlong( finalPath, duration, { autoRotation: false }, (status) => { console.log(`动画状态:${status}`); if (status === 'complete') { // 反向播放时,直接使用finalPath的反向,无需依赖route参数 const reversedPath = [...finalPath].reverse(); // 简化参数,避免route.steps异常 initCarAnimation({ steps: [{ path: reversedPath }] // 仅传递必要的路径信息 }); } } ); } else { // 备选动画(无moveAlong时使用) console.warn('使用备选动画方案'); customMoveAnimation(finalPath, duration); } } // 7. 加载动画插件并启动 if (typeof AMap.MoveAnimation === 'undefined') { console.log('正在加载动画插件...'); AMap.plugin('AMap.MoveAnimation', () => { console.log('动画插件加载完成'); startAnimation(); }); } else { startAnimation(); } } // 自定义动画备选方案(当AMap.MoveAnimation不可用时) function customMoveAnimation(path, duration) { let frameIndex = 0; const totalFrames = path.length; const frameDuration = duration / totalFrames; function updatePosition() { if (frameIndex < totalFrames && carMarker) { // 强制设置当前路径点,无插值 carMarker.setPosition(path[frameIndex]); frameIndex++; // 用setTimeout确保时间精准,避免累积误差 setTimeout(updatePosition, frameDuration); } else if (carMarker) { const reversedPath = [...path].reverse(); initCarAnimation({ steps: [{ path: reversedPath }] }); } } updatePosition(); // 直接启动,不依赖requestAnimationFrame(减少延迟) }为什么设置了不随着高德路线路线偏移移动,还是生成的动画一直按照路线偏移移动,并且移动到一半卡住不动
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值