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

相关推荐
2501_9159184122 分钟前
Web 前端可视化开发工具对比 低代码平台、可视化搭建工具、前端可视化编辑器与在线可视化开发环境的实战分析
前端·低代码·ios·小程序·uni-app·编辑器·iphone
程序员的世界你不懂1 小时前
【Flask】测试平台开发,新增说明书编写和展示功能 第二十三篇
java·前端·数据库
索迪迈科技1 小时前
网络请求库——Axios库深度解析
前端·网络·vue.js·北京百思可瑞教育·百思可瑞教育
gnip1 小时前
JavaScript二叉树相关概念
前端
一朵梨花压海棠go2 小时前
html+js实现表格本地筛选
开发语言·javascript·html·ecmascript
attitude.x2 小时前
PyTorch 动态图的灵活性与实用技巧
前端·人工智能·深度学习
β添砖java2 小时前
CSS3核心技术
前端·css·css3
空山新雨(大队长)2 小时前
HTML第八课:HTML4和HTML5的区别
前端·html·html5
猫头虎-前端技术3 小时前
浏览器兼容性问题全解:CSS 前缀、Grid/Flex 布局兼容方案与跨浏览器调试技巧
前端·css·node.js·bootstrap·ecmascript·css3·媒体