每天搞透一道JS手写题💪「Day3手写数组的六种原型方法(forEach、map、filter、every、some、reduce)」

首先我们来复习一下数组原型上这些会对数组进行遍历的操作:

方法 返回值 作用
forEach 无返回值 遍历数组,对每个元素执行回调函数
map 新数组 遍历数组,对每个元素执行回调函数并返回新的元素,组成新的数组
filter 新数组 遍历数组,筛选出满足条件的元素,组成新的数组
every 布尔值 遍历数组,判断是否所有元素都满足条件
some 布尔值 遍历数组,判断是否有至少一个元素满足条件
reduce 累积值 遍历数组,对累积值和当前元素执行回调函数,返回最终的累积值

他们都接受一个回调函数和一个可选值作为参数:

forEach、map、filter、every、some这五种方法的回调函数都接受三个参数,分别是:

  1. 当前元素(element):数组中正在处理的当前元素。
  2. 当前索引(index):数组中正在处理的当前元素的索引。
  3. 数组(array):调用了该方法的数组。

可选值是 thisArg,执行 callbackFn 时用作 this 的值,如果没有传入,则回调函数中的 this 值为 undefined

reduce方法的回调函数接受四个参数,分别是:

  1. 累积值(accumulator):累积器累积回调函数的返回值;它是上一次调用回调函数时返回的累积值,或者初始值(如果提供了的话)。
  2. 当前元素(currentValue):数组中正在处理的当前元素。
  3. 当前索引(currentIndex):数组中正在处理的当前元素的索引。
  4. 数组(array):调用了reduce方法的数组。

可选值是 initialValue ,第一次调用回调时初始化 accumulator 的值。如果指定了 initialValue,则 callbackFn 从数组中的第一个值作为 currentValue 开始执行。如果没有指定 initialValue,则 accumulator 初始化为数组中的第一个值,并且 callbackFn 从数组中的第二个值作为 currentValue 开始执行。在这种情况下,如果数组为空(没有第一个值可以作为 accumulator 返回),则会抛出错误。

好了,大致上的功能和使用方式就是这样,接下来让我们动手实现他们吧:

forEach

js 复制代码
/**
 * 使用ES6语法实现数组的forEach方法
 * @param {Function} callback - 回调函数
 * @param {Object} [thisArg] - 回调函数中的this值
 */
Array.prototype.myForEach = function(callback, thisArg) {
  // 判断callback是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }

  // 获取数组长度
  let len = this.length;

  // 遍历数组
  for (let i = 0; i < len; i++) {
    // 判断数组中该元素是否存在
    if (Object.prototype.hasOwnProperty.call(this, i)) {
      // 调用回调函数,改变this指向为thisArg
      callback.call(thisArg, this[i], i, this);
    }
  }
}

map

js 复制代码
/**
 * 使用ES6语法实现数组的map方法
 * @param {Function} callback - 回调函数
 * @param {Object} [thisArg] - 回调函数中的this值
 * @return {Array} - 一个新数组,每个元素都是回调函数的返回值
 */
Array.prototype.myMap = function(callback, thisArg) {
  // 判断callback是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }

  // 获取数组长度
  let len = this.length;

  // 创建新数组
  let result = new Array(len);

  // 遍历数组
  for (let i = 0; i < len; i++) {
    // 判断数组中该元素是否存在
    if (Object.prototype.hasOwnProperty.call(this, i)) {
      // 调用回调函数,改变this指向为thisArg
      result[i] = callback.call(thisArg, this[i], i, this);
    }
  }

  // 返回新数组
  return result;
}

filter

js 复制代码
/**
 * 使用ES6语法实现数组的filter方法
 * @param {Function} callback - 回调函数
 * @param {Object} [thisArg] - 回调函数中的this值
 * @return {Array} - 一个新数组,包含所有满足条件的元素
 */
Array.prototype.myFilter = function(callback, thisArg) {
  // 判断callback是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }

  // 获取数组长度
  let len = this.length;

  // 创建新数组
  let result = [];

  // 遍历数组
  for (let i = 0; i < len; i++) {
    // 判断数组中该元素是否存在
    if (Object.prototype.hasOwnProperty.call(this, i)) {
      // 调用回调函数,改变this指向为thisArg
      if (callback.call(thisArg, this[i], i, this)) {
        result.push(this[i]);
      }
    }
  }

  // 返回新数组
  return result;
}

every

js 复制代码
/**
 * 使用ES6语法实现数组的every方法
 * @param {Function} callback - 回调函数
 * @param {Object} [thisArg] - 回调函数中的this值
 * @return {Boolean} - 如果数组中的每个元素都满足回调函数的测试,则返回true,否则返回false
 */
Array.prototype.myEvery = function(callback, thisArg) {
  // 判断callback是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }

  // 获取数组长度
  let len = this.length;

  // 遍历数组
  for (let i = 0; i < len; i++) {
    // 判断数组中该元素是否存在
    if (Object.prototype.hasOwnProperty.call(this, i)) {
      // 调用回调函数,改变this指向为thisArg
      let result = callback.call(thisArg, this[i], i, this);
      // 如果回调函数返回false,则返回false
      if (!result) {
        return false;
      }
    }
  }

  // 如果遍历完整个数组都没有返回false,则返回true
  return true;
}

some

js 复制代码
/**
 * 使用ES6语法实现数组的some方法
 * @param {Function} callback - 回调函数
 * @param {Object} [thisArg] - 回调函数中的this值
 * @return {Boolean} - 如果数组中至少有一个元素满足回调函数的测试,则返回true,否则返回false
 */
Array.prototype.mySome = function(callback, thisArg) {
  // 判断callback是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }

  // 获取数组长度
  let len = this.length;

  // 遍历数组
  for (let i = 0; i < len; i++) {
    // 判断数组中该元素是否存在
    if (Object.prototype.hasOwnProperty.call(this, i)) {
      // 调用回调函数,改变this指向为thisArg
      let result = callback.call(thisArg, this[i], i, this);
      // 如果回调函数返回true,则返回true
      if (result) {
        return true;
      }
    }
  }

  // 如果遍历完整个数组都没有返回true,则返回false
  return false;
}

reduce

js 复制代码
/**
 * 使用ES6语法实现数组的reduce方法
 * @param {Function} callback - 回调函数
 * @param {any} [initialValue] - 初始值
 * @return {any} - 函数累计处理的结果
 */
Array.prototype.myReduce = function(callback, initialValue) {
  // 判断callback是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(`${callback} is not a function`);
  }

  // 获取数组长度
  let len = this.length;

  // 定义累积值和起始索引
  let accumulator, startIndex;

  // 如果提供了初始值,则累积值为初始值,起始索引为0
  if (arguments.length >= 2) {
    accumulator = initialValue;
    startIndex = 0;
  } else {
    // 如果没有提供初始值,则累积值为数组中第一个存在的元素,起始索引为1
    startIndex = -1;
    for (let i = 0; i < len; i++) {
      if (Object.prototype.hasOwnProperty.call(this, i)) {
        accumulator = this[i];
        startIndex = i + 1;
        break;
      }
    }

    // 如果数组为空且没有提供初始值,则抛出错误
    if (startIndex === -1) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
  }

  // 遍历数组
  for (let i = startIndex; i < len; i++) {
    // 判断数组中该元素是否存在
    if (Object.prototype.hasOwnProperty.call(this, i)) {
      // 调用回调函数,更新累积值
      accumulator = callback(accumulator, this[i], i, this);
    }
  }

  // 返回累积值
  return accumulator;
}

思考

其实只要理解了 forEach 的实现,再往后推导实现其他的方法就不难了。值得注意的是,我们在判断数组中第i位是否存在的时候使用了 Object.prototype.hasOwnProperty.call(this, i),这是为什么呢?这是为了避免数组中的空位带来影响:

js 复制代码
let arr = [1, , undefined];
console.log(arr[1]); // undefined
console.log(arr[2]); // undefined

let arr = [1, , undefined];
console.log(arr.hasOwnProperty(1)); // false
console.log(arr.hasOwnProperty(2)); // true

如果数组中某一位的元素正好是 undefined,那么如果直接通过 this[i] 读取他我们是无法判断这一位到底是空位,还是值为 undefined 的元素。举个例子:

js 复制代码
let arr = [1, , 3];
let result = arr.map(x => x * 2);
console.log(result); // [2, 空, 6]

而如果我们在 myMap 中不做 if(Object.prototype.hasOwnProperty.call(this, i)) 的判断:

js 复制代码
let arr = [1, , 3];
let result = arr.myMap(x => x * 2);
console.log(result); // [2, NaN, 6]

可以发现二者对于空位的返回结果是不同的,因为后者没有跳过对 undefined 元素的处理,导致 undefined 值乘以2,得到 NaN 值。原生的方法中,对空位是会跳过处理的,而对值为 undefined 的元素不会,在我们手写实现的时候要注意到这一点。

相关推荐
我是伪码农5 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king5 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳5 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵6 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星6 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_6 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝6 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions7 小时前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发7 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_7 小时前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html