JS 中数组常用方法以及它的原理实现(三)

1,957 阅读6分钟

开始

前两节对数组的十多个API进行了总结整理,下面继续按照MDN 上的顺序和介绍依次来整理实现相关API。

数组方法

Array.prototype.includes()

作用

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

应用

console.log([NaN,1,2,2,NaN,0].includes(122));//false
console.log([NaN,1,2,2,NaN,0].includes(2,-2));//false
console.log([NaN,1,2,2,NaN].includes(2));//true
console.log([NaN,1,2,2,NaN,0].includes(NaN));//true
console.log([NaN,1,2,2,NaN,0].includes(-0));//true

实现

思路

  • 在数组的原型上定义方法。
  • fromIndex 入参是数字,默认是0(如果不能转为合法数字,默认为0)
  • 比较时注意NaN。该API 任务+0.-0 和0相等,也得注意
  • 如果 fromIndex 为负值,与数组长度相加计算出的索引将作为开始搜索searchElement的位置。如果计算出的索引小于 0,则整个数组都会被搜索。

代码实现

Array.prototype._includes=function(target,fromIndex){
     let data=this
     //转为数字
     fromIndex=fromIndex-0;
     //看是否是合法数字
     if(!fromIndex ||typeof fromIndex!='number')
     {
         fromIndex=0;
     }
     if(fromIndex<0)
     {
         //索引为-1 与数组长度相加,如果还是负数,则从0开始遍历
         fromIndex=(this.length+fromIndex)>0 ?(this.length+fromIndex):0
     }
     for(let i=fromIndex;i<data.length;i++)
     {
         // [NaN].indexOf(NaN) -1  indexof 对NaN 不准确
         //但是API没有处理 +0 和-0,认为 0,+0,-0相等,也不能用Object.is
         //  if(Object.is(data[i],target))
         //  {
         //      return true;
         //  }
         if(data[i]===target)
         {
             return true;
         }
         // 自己都不等于自己,代表是NaN
         if(data[i]!=data[i] && target!=target)
         {
             return true;
         }
     }
   
     return false;
}

测试

使用时需要注意fromIndex 为负数的情况

console.log([NaN,1,2,2,NaN,0].includes(122));//false
console.log([NaN,1,2,2,NaN,0].includes(2,-2));//false
console.log([NaN,1,2,2,NaN].includes(2));//true
console.log([NaN,1,2,2,NaN,0].includes(NaN));//true
console.log([NaN,1,2,2,NaN,0].includes(-0));//true

Array.prototype.indexOf()

作用

indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。既能得到索引,也可以根据返回的是-1判断数组中是否存在目标对象。但是需要注意的是NaN。[NaN].indexOf(NaN) 返回的是-1。

应用

以下是几种常见情况。

console.log([NaN,1,2,2,NaN,0].indexOf(2));//2
console.log([NaN,1,2,2,NaN,0].indexOf(2,4));//-1
console.log([NaN,1,2,2,NaN,0].indexOf(1,-5));//1
console.log([NaN,1,2,2,NaN,0].indexOf(NaN));//-1

实现

思路

  • 在数组的原型上定义方法。
  • 索引值可正也可以为负值,为负值,还是通过与数组长度相加计算索引起始值,不会影响比较顺序。默认为0
  • 不用考虑NaN 的特殊性。

代码实现

其实和includes 方法基本一样。

Array.prototype._indexOf=function(target,fromIndex=0){
    let data=this;
    fromIndex=fromIndex-0 ||0;
    if(!fromIndex ||typeof fromIndex!='number')
    {
        fromIndex=0;
    }
    if(fromIndex<0)
    {
        //索引为-1 与数组长度相加,如果还是负数,则从0开始遍历
        fromIndex=(this.length+fromIndex)>0 ?(this.length+fromIndex):0
    }

    for(let i=fromIndex;i<data.length;i++)
    {
        if(data[i]===target)
        {
            return i;
        }
    }
   //没有匹配返回-1
  
    return -1;
}

测试

经过测试,和原生方法能得到同样的结果。

console.log([NaN,1,2,2,NaN,0]._indexOf(2));//2
console.log([NaN,1,2,2,NaN,0]._indexOf(2,4));//-1
console.log([NaN,1,2,2,NaN,0]._indexOf(1,-5));//1
console.log([NaN,1,2,2,NaN,0]._indexOf(NaN));//-1

Array.prototype.lastIndexOf()

作用

lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。 从名称就看出和indexOf的区别就是它返回的是最后一个索引。 基本实现方法和基本一致。

实现

思路

  • 与indexOf 区别就是逆向查找

代码实现

Array.prototype._lastIndexOf=function(target,fromIndex=0){
    let data=this;
    fromIndex=fromIndex-0 ||0;
    if(!fromIndex ||typeof fromIndex!='number')
    {
        fromIndex=this.length-1;
    }
    if(fromIndex<0)
    {
        //索引为-1 与数组长度相加,如果还是负数,则从0开始遍历
        fromIndex=(this.length+fromIndex)<this.length-1 ?(this.length+fromIndex):this.length-1
    }
    for(let i=fromIndex;i>=0;i--)
    {
        if(data[i]===target)
        {
            return i;
        }
    }
   //没有匹配返回-1
  
    return -1;
}

测试

得到如下效果。

var array = [2, 5, 9, 2];
var index = array._lastIndexOf(2);
console.log(index) //3
var index2= array._lastIndexOf(2,-1);
console.log(index2)//3
var index2= array._lastIndexOf(2,2);
console.log(index2)//0
var index3= array._lastIndexOf(2,-2);
console.log(index3)//0
var index4= array._lastIndexOf(6);
console.log(index4)//-1

Array.prototype.map()

作用

map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

这也是一个很常用的方法,特别在React中,到处都是,其实很简单,它与forEach 方法唯一的区别就是将回调返回值存贮到数组中并返回。

实现

思路

*利用for 循环实现即可。

代码实现

Array.prototype._map=function(callback,thisArg){
    if(typeof callback!='function')
    {
        throw new TypeError('callback is not a function')
    }
    let context=thisArg || this;

    let res=[];
    for(let i=0;i<this.length;i++)
    {
         let value=this[i];
          res.push(callback.call(context,value));
    }

    return res;
}

测试

很简单,就不再多言。

const array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1._map(x => x * 2);
console.log(map1); //[ 2, 8, 18, 32 ]
const map = array1._map(function(x){
    return x*(this.n)

},{n:3});

console.log(map); //[ 3, 12, 27, 48 ]

Array.prototype.reduce()

作用

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

这是一个很重要很常用的API,实现累加的效果,比如数组求和,react中常用到的复合函数

应用

  • 数组求和
//无初始值
console.log([1,2,3,4].reduce((cur,next)=>cur+next)) //10
//有初始值
console.log([1,2,3,4].reduce((cur,next)=>cur+next,6)) //16
  • 复合函数(React中常用)

function compose(...fns){
    if(fns.length===0)
    {
        return false;
    }
    if(fns.length==1)
    {
        return fns[0];
    }
    return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}

function A(name){
    return name+'A执行';
}
function B(name){
    return name+'B执行';
}
function C(name){
    return name+'C执行';
}
let fn=compose(A,B,C) //A(B(C(...args)) //从里往外执行
console.log(fn('begin:'))//begin:C执行B执行A执行

实现

思路

  • 在数组的原型对象定义方法
  • 无初始值,取数组第一项。
  • 不能改变原数组。
  • 空数组执行抛出异常

代码实现

Array.prototype._reduce=function(accumulator,currentValue){
    if(typeof accumulator!='function')
    {
        throw new TypeError('accumulator is not a function')
    }
    if(this.length===0)
    {
        throw new Error('Error: Reduce of empty array with no initial value')
    }
    //数组复制一份(为了简单,本应该写原理应该实现数组的复制,或者不应该采用shift方法)
     let arr=[...this];
      if(!currentValue && currentValue!=0)
     {
        currentValue=arr.shift();
     }
    let cur=currentValue;
    arr.forEach((item,index)=>{
        cur=accumulator(cur,item,index,this);
    });
    return cur;

}

测试

同样也能实现如下效果。

console.log([1,2,3,4]._reduce((cur,next)=>cur+next)) //10
console.log([1,2,3,4]._reduce((cur,next)=>cur+next,6)) //16
let fn=compose(A,B,C)
console.log(fn('begin:'))//begin:C执行B执行A执行

还有一个Array.prototype.reduceRight() 方法,从字面量就能看出只是遍历顺序从右往左了而已。实现时只需要把数组reverse就行了,在此就不做多的介绍了。

结束

相关链接