给我十分钟,手把手教你实现Javascript数组原型对象上的七个方法!

2,351 阅读6分钟

一、forEach

1.语法和参数

我们可以先在MDN上查看forEach方法的语法和参数 image.png

2.基本使用方法

const arr = ['a', 'b', 'c', 'd', 'e'];
const obj = {name:"tom"}
 arr.forEach(function (item, index, arr) {
          console.log(item, index, arr, this);
      },obj);

MDN文档中可以看到,forEach 接受两个参数:

  1. 一个回调函数,这个回调函数为数组中每个元素需要执行的函数
  2. 一个this指向值,可选参数
Array.prototype.myForEach= function(cb){
  //myForEach方法是由数组调用:arr.forEach(...),所以当前方法下的this就是指向数组本身
  const _arr = this;
  //获取第二个参数,也就是需要改变的this指向值,这个参数是可选的,用于改变this指向
  const _thisArg = arguments[1]||window;
}

拿到了外面传的参数(1.回调函数 2.需要改变的this指向值)和数组本身以后,我们只需要加上 myForEach 函数的处理逻辑就大功告成了,那么myForEach里面具体要做什么事情呢?其实很简单:就是循环遍历数组,让数组的每个元素执行一次回调函数就ok了

3.实现代码

Array.prototype.myForEach= function(cb){
  //myForEach方法是由数组调用:arr.forEach(...),所以当前方法下的this就是指向数组本身
  const _arr = this;

  //获取第二个参数,也就是需要改变的this指向值,这个参数是可选的,用于改变this指向
  const _thisArg = arguments[1]||window;
  
  //循环遍历
  for(var i = 0;i<_arr.length; i++){
    //外面传入的回调函数里具体是有3个参数,分别是当前值,索引值,当前数组
    //我们这里函数的调用需要借用apply来改变this指向
     cb.apply(_thisArg, [_arr[i], i, _arr]);
  }
  //返回undefined
  //return undefined
}

二、map

1.语法与参数

map方法的参数与语法跟forEach差别不大,区别在于map方法是有返回值的,返回值的类型为一个新数组. 我们可以在上面myForEach的基础上进行改造

Array.prototype.myMap = function (cb) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[1] || window; //接受的第二个参数,可能有,可能没有.用于改变this指向
  var _res; //如果没有任何返回,就是undefined
  var result = [];
  var _item;
  for (var i = 0; i < _length; i++) {
    _item = _arr[i]; 
    //同样是使用apply函数来进行this指向的改变
    _res = cb.apply(_args2, [_item, i, _arr]);
    //将回调函数的返回值放入结果集中
    result.push(_res);
  }
  return result;
};

上面的代码实现是有小瑕疵的,直接使用会出现我们常说的"浅拷贝"现象.详情请看下图控制台输出

image.png

我们把mapResult的第一项中的a值修改成100,原数组的第一项中的a值也会被影响

image.png

所以我们必须在代码中对数组中的值进行深拷贝处理,借助deepClone工具函数,deepClone代码我会放到文章最后

2.实现代码

Array.prototype.myMap = function (cb) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[1] || window; //接受的第二个参数,可能有,可能没有.用于改变this指向
  var _res; //如果没有任何返回,就是undefined
  var result = [];
  var _item;
  for (var i = 0; i < _length; i++) {
    _item = deepClone(_arr[i]); //小心对象引用的复制
    _res = cb.apply(_args2, [_item, i, _arr]);
    result.push(_res);
  }
  return result;
};

三、every

有了之前的基础,我们就不再赘述Every函数的用法和参数了,直接上代码.

Every方法的区别在于,所有数组项的回调函数结果返回为true,result才为true,否则则为false

Array.prototype.myEvery = function (cb) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[1] || window; //接受的第二个参数,可能有,可能没有.用于改变this指向
  var _res; //如果没有任何返回,就是undefined
  var result = true;
  var _item;
  for (var i = 0; i < _length; i++) {
    _item = deepClone(_arr[i]); //小心对象引用的复制
    //有一个是false,结果则为false
    if (!cb.apply(_args2, [_item, i, _arr])) {
      return false;
    }
  }
  return true;
};

四、some

some方法的区别在于,只要有一个数组项的回调函数结果返回true,result就为true,否则则为false

Array.prototype.mySome = function (cb) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[1] || window; //接受的第二个参数,可能有,可能没有.用于改变this指向
  var _res; //如果没有任何返回,就是undefined
  var result = false;
  var _item;
  for (var i = 0; i < _length; i++) {
    _item = deepClone(_arr[i]); //小心对象引用的复制
    if (cb.apply(_args2, [_item, i, _arr])) {
      //有一个是true,结果则为true
      return true;
    }
  }
  return result;
};

五、filter

filter方法的区别在于,只有数组项的回调函数结果返回true,我们才将它push进result中

Array.prototype.myFilter = function (cb) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[1] || window; //接受的第二个参数,可能有,可能没有.用于改变this指向
  var _res; //如果没有任何返回,就是undefined
  var result = [];
  var _item;
  for (var i = 0; i < _length; i++) {
    _item = deepClone(_arr[i]); //小心对象引用的复制
    //cb的返回结果为true,我们才放入result中
    cb.apply(_args2, [_item, i, _arr]) ? result.push(_item) : "";
  }
  return result;
};

六、reduce

在编写myReduce方法前,我们需要特别注意的是reduce接收的回调函数比其他几个方法接收的回调函数多了一个previousValue参数.

image.png

如上图所示,previousValue就是上一次调用回调函数的返回值,这个是最关键的点!!!

而当数组元素进行循环遍历执行回调函数后的结果就是这个previousValue

Array.prototype.myReduce = function (cb, initialValue) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[2] || window; //接受的第三个参数,可能有,可能没有.用于改变this指向

  var _item;
  for (var i = 0; i < _length; i++) {
    _item = deepClone(_arr[i]); //小心对象引用的复制
    initialValue = cb.apply(_args2, [initialValue, _item, i, _arr]);
  }
  return initialValue;
};

七、reduceRight

reduceRight跟reduce的区别就在于讲数组的元素从右到左(从后到前)调用,我们只需要在myReduce的基础上修改遍历顺序即可.

Array.prototype.myReduceRight = function (cb, initialValue) {
  //谁调用当前方法,this就是谁(当前数组)
  var _arr = this;
  var _length = _arr.length;
  var _args2 = arguments[2] || window; //接受的第三个参数,可能有,可能没有.用于改变this指向

  var _item;
  for (var i = _length; i >= 0; i--) {
    _item = deepClone(_arr[i]); //小心对象引用的复制
    initialValue = cb.apply(_args2, [initialValue, _item, i, _arr]);
  }
  return initialValue;
};

八、总结

以上就是我们自己实现的Javascript数组原型对象上的七个方法,你学废了吗?

附上deepClone工具函数代码:

function deepClone(origin, target) {
  var tar = target || {};
  var toStr = Object.prototype.toString;
  var arrayType = "[Object Array]";

  for (var k in origin) {
    if (origin.hasOwnProperty(k)) {
      if (typeof origin[k] === "object" && origin[k] != null) {
        tar[k] = toStr.call(origin[k]) === arrayType ? [] : {};
        deepClone(origin[k], tar[k]);
      } else {
        tar[k] = origin[k];
      }
    }
  }

  return tar;
}