38.实现 Array.prototype.map、reduce 副本.md 来源:https://my.feishu.cn/file/GX7YbXOi7ox07Rxz8tHcDlljnJd 采集状态:已完成正文采集;原文件源码视图逐行采集,行号 1 至 228 连续,滚动到底。 ## 1. 核心概念 (Core Concept) `Array.prototype.map()` 和 `Array.prototype.reduce()` 是 JavaScript 数组的两个非常重要且常用的高阶函数,它们都用于处理数组元素并返回新的结果,但处理方式和返回结果的形态不同。`map` 用于将数组的每个 ელემენტი 映射到 नए 값이에 对应,返回一个相同长度的新数组;`reduce` 用于将数组 ელემენტი 累积到一个单一值(或其他类型的值)。 ## 2. 为什么需要它? (The "Why") 1. **函数式编程风格 (Functional Programming Style):** 它们是函数式编程范式的体现,通过纯函数处理数据,避免副作用,使代码更可读、易于测试和维护。 2. **简洁高效 (Concise & Efficient):** 它们提供了一种声明式的方式来处理数组,相比传统的 `for` 或 `while` 循环,代码更简洁,意图更清晰。 3. **通用性 (Versatility):** `map` 和 `reduce` 可以应对各种数组转换和聚合的场景,是处理数组的强大工具。 ## 3. API 与用法 (API & Usage) 本节将基于 ECMAScript 规范和 MDN 文档来描述这两个方法的实现原理和核心用法。 ### 3.1 `Array.prototype.map()` **签名 (Simplified):** ```typescript array.map(callback(currentValue, index, array), thisArg) ``` **参数:** * `callback`: 回调函数,为数组中每个元素执行的函数。其返回值将构成新数组中对应位置的值。 * `currentValue`: 数组中正在处理的当前元素。 * `index` (可选): 数组中正在处理的当前元素的索引。 * `array` (可选): `map` 方法被调用的数组。 * `thisArg` (可选): 执行 `callback` 函数时使用的 `this` 值。 **返回值:** 一个由原数组中的每个 ელემენტი 调用 `callback` 函数的返回值组成的新数组。 **实现原理 (Simplified):** 1. 创建一个新的空数组 `resultArray`。 2. 遍历原数组的 ელემენტი。 3. 对于每一个 ელემენტი,以 `currentValue`, `index`, `array` 为参数调用 `callback` 函数(若提供了 `thisArg`,则绑定 `this`)。 4. 将 `callback` 函数的返回值添加到 `resultArray` 中。 5. 遍历结束后,返回 `resultArray`。 **代码示例 (模拟实现):** ```javascript if (!Array.prototype.map) { Array.prototype.map = function(callback, thisArg) { 'use strict'; // 确保 this 是一个对象,并处理 null 或 undefined 的情况 if (this == null) { throw new TypeError('Array.prototype.map called on null or undefined'); } // 将 this 转换为对象 var O = Object(this); // 获取数组长度,确保 length 是一个无符号 32 位整数 var len = O.length >>> 0; // Ensure callback is a function if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } // 创建结果数组 var A = new Array(len); // 遍历数组 var k = 0; while (k < len) { var kValue, mappedValue; // 检查属性是否存在 (处理稀疏数组) if (k in O) { kValue = O[k]; // 调用回调函数,并传入适当的 this 值 // 使用 call 或 apply 来设置 this 和参数 mappedValue = callback.call(thisArg, kValue, k, O); // 将结果赋值给新数组 A[k] = mappedValue; } // else { empty slots in array-likes are dropped by Array.prototype.map after ES5 } k++; } // 返回新数组 return A; }; } // 示例用法 const numbers = [1, 4, 9]; const roots = numbers.map(Math.sqrt); console.log(roots); // [1, 2, 3] const listItems = ['apple', 'banana']; const htmlList = listItems.map(item => `
  • ${item}
  • `); console.log(htmlList); // ['
  • apple
  • ', '
  • banana
  • '] ``` ### 3.2 `Array.prototype.reduce()` **签名 (Simplified):** ```typescript array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue) ``` **参数:** * `callback`: 回调函数,对数组中的每个元素按顺序执行。 * `accumulator`: 累积器,存储上一次调用回调函数的返回值。或者 `initialValue`(如果提供的话)。 * `currentValue`: 数组中正在处理的当前元素。 * `currentIndex` (可选): 数组中正在处理的当前元素的索引。如果 `initialValue` 存在,则从索引 0 开始;否则从索引 1 开始。 * `array` (可选): `reduce` 方法被调用的数组。 * `initialValue` (可选): 作为第一次调用 `callback` 函数时的 `accumulator` 的初始值。如果未提供,则使用数组的第一个元素作为 `initialValue`,并从第二个元素开始执行 `callback`。 **返回值:** 累积计算的最终结果。 **实现原理 (Simplified):** 1. 判断是否提供 `initialValue`。 2. 如果提供了 `initialValue`,则 `accumulator` 初始化为 `initialValue`,遍历从索引 0 开始。 3. 如果没有提供 `initialValue`: * 检查数组是否为空。如果为空,抛出 `TypeError`。 * `accumulator` 初始化为数组的第一个 ელემენტი,遍历从索引 1 开始。 4. 遍历数组的 ელემენტი (从适当的起始索引)。 5. 对于每个 ელემენტი,以当前的 `accumulator`, `currentValue`, `currentIndex`, `array` 为参数调用 `callback` 函数。 6. 将 `callback` 函数的返回值更新为新的 `accumulator`。 7. 遍历结束后,返回最终的 `accumulator` 值。 **代码示例 (模拟实现):** ```javascript if (!Array.prototype.reduce) { Object.defineProperty(Array.prototype, 'reduce', { value: function(callback /*, initialValue*/) { 'use strict'; if (this == null) { throw new TypeError('Array.prototype.reduce called on null or undefined'); } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); } var O = Object(this); var len = O.length >>> 0; var k = 0; var value; var isInitialValuePresent = arguments.length >= 2; if (isInitialValuePresent) { value = arguments[1]; } else { // 如果没有提供初始值,使用数组的第一个元素 while (k < len && !(k in O)) { k++; } // 如果数组是空的或者只有空槽,且没有提供初始值 if (k >= len) { throw new TypeError('Reduce of empty array with no initial value'); } value = O[k++]; // 使用第一个存在的元素作为初始值,并从下一个元素开始遍历 } while (k < len) { if (k in O) { // 只处理存在的属性 // 调用回调函数更新累积值 value = callback(value, O[k], k, O); } k++; } // 返回最终的累积值 return value; } }); } // 示例用法 1: 求和 const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); console.log(sum); // 10 // 示例用法 2: 展平数组 const nestedArray = [[1, 2], [3, 4], [5]]; const flatArray = nestedArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); console.log(flatArray); // [1, 2, 3, 4, 5] // 示例用法 3: 计算数组中每个元素出现的次数 const names = ['Alice', 'Bob', 'Tiff', 'Alice', 'Bob']; const countedNames = names.reduce((allNames, name) => { const currentCount = allNames[name] ?? 0; return { ...allNames, [name]: currentCount + 1, }; }, {}); // 初始值是一个空对象 console.log(countedNames); // { Alice: 2, Bob: 2, Tiff: 1 } ``` ## 4. 关键注意事项 (Key Considerations) 1. **`map` 的返回值是新数组:** `map` 方法总是返回一个**新的**数组,不会修改原数组。这是函数式编程“不可变性”原则的体现。 2. **`reduce` 的初始值:** 提供 `initialValue` 是推荐的最佳实践,特别是当数组可能为空时,可以避免因没有初始值而抛出 `TypeError`。同时,提供了初始值,`reduce` 的逻辑会更稳定和易于理解,它保证了回调函数被调用的次数与数组长度相同(对于非稀疏数组)。 3. **稀疏数组的处理:** 在 ES5+ 版本中,`map` 和 `reduce` (以及 `forEach`, `filter` 等) 在遍历时会跳过数组中的空槽(`empty` items,通过 `k in O` 判断是否存在属性)。模拟实现时需要注意这一点。 4. **修改原数组可能导致问题:** 在 `map` 或 `reduce` 的回调函数中修改正在遍历的原数组可能会导致意外行为,应尽量避免。 ## 5. 参考资料 (References) * **MDN Web Docs:** * [`Array.prototype.map()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map) * [`Array.prototype.reduce()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) * **ECMAScript® 2024 Language Specification:** * [Array.prototype.map](https://tc39.es/ecma262/#sec-array.prototype.map) * [Array.prototype.reduce](https://tc39.es/ecma262/#sec-array.prototype.reduce) --- 原文链接与附件索引 `Array.prototype.map()` https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map `Array.prototype.reduce()` https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce Array.prototype.map https://tc39.es/ecma262/#sec-array.prototype.map Array.prototype.reduce https://tc39.es/ecma262/#sec-array.prototype.reduce