每天搞透一道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 的元素不会,在我们手写实现的时候要注意到这一点。

相关推荐
一颗花生米。2 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&3 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch7 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光7 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   7 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发