资讯专栏INFORMATION COLUMN

一起来实现es5中新增的数组方法

LiuRhoRamen / 2398人阅读

摘要:再将其返回,注意是返回一个新的数组,而不是将原数组直接改变使用方式和类似也是接受一个回调函数,一个改变内部指向的对象。

前言

一直以来想写一篇关于es5中新增数组的使用方法与源码实现的文章,拖了挺久了,趁着这夜深人静,大脑清醒,又困意不在的时刻写下来。也许有人会问,现如今es6都大行其道了,还学es5是不是有些过时了,?,温故而知新,可以从中多学点嘛,而且我们是要自己实现这些方法,知其然还要知其所以然,不光要会用,还要知道为什么是这样用哒。

新增方法预览

es5中给js的数组增添了许多实用的方法,利用这些方法可以帮助我们更加快速方便的写js代码,然后蛋疼的是低版本ie肯定是不支持的,所以..................自己动手丰衣足食。让我们一步步看下如何使用与实现这些方法。

forEach

map

filter

some

every

indexOf

lastIndexOf

reduce

reduceRight

forEach

这个方法作用是啥咧,就是循环,遍历。比如一般我们在for循环做这样的事的时候如下。

var arr = [1, 2, 3, 4, 5, 6];

for (var i = 0, len = arr.length; i < len; i++) {
  console.log(arr[i], i, arr);
}

如果用forEach我们应该如何做呢?

var arr = [1, 2, 3, 4, 5, 6];

arr.forEach(function (e, i, array) {
  console.log(e, i, array)
})

是不是觉得不用写for循环了,瞬间逼格都高了

forEach函数中的回调函数支持三个参数,1、数组的值2、值的索引3、数组本身。这样的调用方式是不是和jQuery中的$.each很像? 其实不然,jQuery和forEach回调函数的第一个和第二个参数正好是反着来的。

看看对比

var arr = [1, 2, 3, 4, 5];

// forEach
arr.forEach(function (e, i, array) {
  console.log(e, i, array);
})

// output

1 0 [1, 2, 3, 4, 5]
2 1 [1, 2, 3, 4, 5]
3 2 [1, 2, 3, 4, 5]
4 3 [1, 2, 3, 4, 5]
5 4 [1, 2, 3, 4, 5]


// $.each
$.each(arr, function (i, e, array) { // 测试的时候发现array是undefined,查了文档也发现没有第三个参数
  console.log(i, e, array);
})

// output

0 1 undefined
1 2 undefined
2 3 undefined
3 4 undefined
4 5 undefined

接着我们来看一下forEach的第二个参数,这个参数决定第一个回调函数的内部this指向

var arr = [1, 2, 3, 4, 5];

//  默认情况下,第二个参数不传入时
arr.forEach(function (e, i, array) {
  console.log(e, i, array, this);
})

// output
1 0 [1, 2, 3, 4, 5] window 
2 1 [1, 2, 3, 4, 5] window 
3 2 [1, 2, 3, 4, 5] window 
4 3 [1, 2, 3, 4, 5] window 
5 4 [1, 2, 3, 4, 5] window 

// 传入参数
arr.forEach(function (e, i, array) {
  console.log(e, i, array, this);
}, {name: "qianlong"})

// output
1 0 [1, 2, 3, 4, 5] {name: "qianlong"} 
2 1 [1, 2, 3, 4, 5] {name: "qianlong"} 
3 2 [1, 2, 3, 4, 5] {name: "qianlong"} 
4 3 [1, 2, 3, 4, 5] {name: "qianlong"} 
5 4 [1, 2, 3, 4, 5] {name: "qianlong"} 

最后接下来我们自己实现一下这个方法

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeArray = ObjPro.forEach;

Array.prototype.forEach = nativeArray || function (callBack, ctx) {  
  if (typeof callBack != "function") return;
  
  for (var i =0, len = this.length; i < len; i++) {
    if (hasOwn.call(this, i)) {
      callBack.call(ctx, this[i], i, this);
    }
  }
}
map

map是干嘛的! 其最主要的作用就是将原数组按照一定的规则映射成一个新的数组。再将其返回,注意是返回一个新的数组,而不是将原数组直接改变使用方式和forEach类似,也是接受一个回调函数,一个改变内部this指向的对象。

map

array.map(callback,[ thisObject])

callback

var arr = [1, 2, 3, 4, 5];

arr.map(function(value, index, array) {
  
});

举个栗子

var arr = [1, 2, 3, 4, 5];

var newArr = arr.map(function (e, i, array) {
  return "hello " +  e;
})

// output

["hello 1", "hello 2", "hello 3", "hello 4", "hello 5"] // newArr

[1, 2, 3, 4, 5] // arr

注意上面的return,如果我们不写return会怎样呢?

var arr = [1, 2, 3, 4, 5];

var newArr = arr.map(function (e, i, array) {
  "hello " +  e;
})

// output

[undefined, undefined, undefined, undefined, undefined] // newArr

[1, 2, 3, 4, 5] // arr

这一堆的undefined是啥情况,还记得一个函数执行完,如果没有显示的返回值,会返回什么吗? 没错 就是undefined,这就是原因所在,等会通过源码,你就会更加明白。

最后我们自己实现一下map这个方法

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeMap = ObjPro.map;
    
Array.prototype.map = nativeMap || function (callBack, ctx) {
  if (typeof callBack != "function") return;
  
  var returnArr = [];
  for(var i = 0, len = this.length; i < len; i++) {
    returnArr.push(callBack.call(ctx, this[i], i, this)); // 这就是为什么回调函数没有返回值的情况下会得到一堆的undefined值,他将回调函数的返回值push到了一个数组里面,当你没有显示的返回值的时候,自然push进去的就是undefined了     
  }
  return  returnArr;
}    
filter

接下来是filter,筛选,过滤的意思,给你一个数组,用一些你制定的条件,对其中的值进行过滤,最后得到你想要的新的数组。基本用法和map差不多

  
array.filter(callback,[ thisObject]);

但是和map也有差别的地方,filter需要你在callback处返回弱等于true 的值,才会将原数组中筛选出的值返回给你。

举个栗子

var arr = [0, 1, 2, 3, 4, 5];

var newArr = arr.filter(function (e, i, array) {
  return e;
})

// output

[1, 2, 3, 4, 5] // newArr

var newArr2 = arr.filter(function (e, i, array) {
  if (e >= 2) return true; 
})

// ouput
[2, 3, 4, 5] // newArr2

当然最后还有第二个参数改变内部this指向的参数可选,默认是window对象,你也可以传一个对象进去, 最后我们自己来实现一下这个api

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeFilter = ObjPro.filter;
    
Array.prototype.filter = nativeFilter || function (callBack, ctx) {
  if (typeof callBack != "function") return;
  
  var returnArr = [];
  for(var i = 0, len = this.length; i < len; i++) {
    if (callBack.call(ctx, this[i], i, this)) {
      returnArr.push(this[i]);
    }
  }    
  return returnArr;
}

some vs every

some与接下里的every正好相对,some是只要数组中的某个值,符合你给定的判断条件就返回true,而every则是数组中的所有值都符合你给定的判断条件的时候才会返回true,否则就返回false,也就是说两个方法最后得到的都是true or false

举个栗子

    
var arr = [0, 1, 2, 3, 4, 5];

var result = arr.some(function (e, i, array) {
  if (e === 3) {return true};
});

// output      
true // result;

var arr = [0, 1, 2, 3, 4, 5];

var result2 = arr.every(function (e, i, array) {
  if (e > 3) {return true};
});
// output      
false // result;

some 和 every使用起来非常简单,接下来我们自己实现一把

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeSome = ObjPro.some,
    nativeEvery = ObjPro.every;
    
// some    
Array.prototype.some = nativeSome || function (callBack, ctx) {
  if (typeof callBack != "function") return;
  
  var resultValue = false;
  for(var i = 0, len = this.length; i < len; i++) {
    if (resultValue) {
      break;
    }
    resultValue = !!callBack.call(ctx, this[i], i, this); 
  }
  return  resultValue;
} 

// every

Array.prototype.every = nativeEvery || function (callBack, ctx) {
  if (typeof callBack != "function") return;

  var resultValue = true;
  for (var i = 0, len = this.length; i < len; i++) {
    if (!resultValue) {
      break;    
    }
    resultValue = !!callBack.call(ctx, this[i], i, this);
  }    
  return resultValue;      
}   
indexOf

数组的indexOf方法和字符串的indexOf用法非常类似,array.indexOf(searchElement[, fromIndex]),针对给定的要查找的值,和开始查找的位置(可选),返回整数索引值。

举个例子

var arr = [0, 1, 2, 3, 4, 5];

arr.indexOf(1) // 1
arr.indexOf(3, "qianlong") // 3 因为给定的开始索引值不能转化成数字,所以还是从0位置开始搜索 
arr.indexOf(3, 4) // -1
arr.indexOf(3, "4") // -1
arr.indexOf("3") // -1 // 判断条件是强 3 !== "3" => -1

实现代码

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeIndexOf = ObjPro.indexOf;
    
Array.prototype.indexOf = nativeIndexOf || function (searchElement, fromIndex) {
  var returnIndex = -1,
      fromIndex = fromIndex * 1 || 0;
  for (var i = fromIndex, len = this.length; i < len; i++) {
    if (searchElement === this[i]) {
       returnIndex = i;
       break;
    }
  }             
  return returnIndex;        
}

lastIndexOf

数组的lastIndexOf方法和字符串的lastIndexOf用法非常类似,array. lastIndexOf(searchElement[, fromIndex]),针对给定的要查找的值,和开始查找的位置(可选),返回整数索引值。与indexOf不同的地方在于,它是从后往前查找。默认开始查找的位置是 array.length - 1

举个栗子

var arr = [0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0];

arr.lastIndexOf(1) // 9
arr.lastIndexOf(3, "qianlong") // -1 这里和indexOf不一样,传入的值不能转化为数字将得到-1
arr.lastIndexOf(3, 4) // 3
arr.lastIndexOf(3, "4") // 3
arr.lastIndexOf("3") // -1 // 判断条件是强 3 !== "3" => -1

源码实现

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeLastIndexOf = ObjPro.lastIndexOf;
    
Array.prototype.lastIndexOf = nativeLastIndexOf || function (searchElement, fromIndex) {
  var len = this.length,
      returnIndex = -1,
      fromIndex = fromIndex * 1 || len - 1;
      
  for (var i = fromIndex; i > -1; i -= 1) {
    if (this[i] === searchElement){
      returnIndex = i;
      break;
    }
  }
  return returnIndex;
}
reduce

reduce 相对es5中数添加的其他方法都复杂一些,我们可以通过栗子来看一下这个api怎么使用。首先基本参数如下

array.reduce(callback[, initialValue]),接收一个回调函数,一个初始化的值initialValue 。其中callback参数分别是初始化的值initialValue ,如果没有传入initialValue,则默认是数组的第一项。第二个及其后面的参数分别是当前值,索引,数组本身

var arr = [0, 1, 2, 3, 4, 5],
    sum = arr.reduce(function (init, cur, i, array) {
      return init + cur;
    });    
    
   //output
   sum // 15
   

我们来看一下上面的执行过程是怎样的。

第一回合

// 因为initialValue没有传入所以回调函数的第一个参数为数组的第一项

init = 0;
cur = 1;

=> init + cur = 1;

第二回合

init = 1;
cur = 2;

=> init + cur = 3;

第三回合

init = 3;
cur = 3;

=> init + cur = 6;

第四回合

init = 6;
cur = 4;

=> init + cur = 10;

第五回合

init = 10;
cur = 5;

=> init + cur = 15;

最后得到结果15

那么我们如何自己实现一个reduce呢?

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeReduce = ObjPro.reduce;
    
Array.prototype.reduce = nativeReduce || function (callBack, initialVal) {
  if (typeof callBack != "function") return;
  
  var init = initialVal,
      i = 0;
  
      if (init === void (0)) {
        init = this[0];
        i = 1;
      }
  for (i, len = this.length; i < len; i++) {
    if (hasOwn.call(this, i)) {
      init = callBack(init, this[i], i, this);
    }  
  }
  return init;    
}   
    
reduceRight

reduceRight基本用法与reduce类似,好比indexOf与lastIndexOf,不同之处在于它是从最右边的值开始计算的。我们直接去看源码怎么实现吧

var ObjPro = Object.prototype,
    hasOwn = ObjPro.hasOwnProperty,
    nativeReduceRight = ObjPro.reduceRight;
   
Array.prototype.reduceRight = nativeReduceRight || function (callBack, initialVal) {
  if (typeof callBack != "function") return;
  
  var init = initialVal,
      len = this.length,
      i = len - 1;
      
  if (init === void(0)) {
    init = this[len - 1];
    i -= 1;
  }
  for (i; i > -1; i -=1) {
    if (hasOwn.call(this, i)) {
      init = callBack(init, this[i], i, this);
    }
  }    
  return init;
}     
结尾

终于写完了,断断续续快写了两天,欢迎大家看了以后提一些意见,函数实现的不一定都对,肯定有一些问题的地方,欢迎大家指正。

最后把代码放到github上面了

github地址

各位大大,请让我打个小广告。???

博客地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91042.html

相关文章

  • 数组去重--这几种方法够不?

    摘要:数组去重是校招面试的必考知识点。以下就是笔者所实现的数组去重的几种简单的方式。结合实现这种方法的关键点就是判断是否相同的时候不要忽略对元素类型的判断。以上就是笔者所想到的几个数组去重的方式大家如果有更好的方法欢迎留言。 数组去重,是校招面试的必考知识点。简单的说,数组去重就是将一个数组中的相同的元素删除,只保留其中的一个。这里的相同其实是一个陷阱,有好多同学只认为值相等即为相同,而忽略...

    wendux 评论0 收藏0
  • JS每日题:Es6中新增的数据类型有哪些?使用场景?

    摘要:问中新增的数据类型有哪些使用场景中新增一种原始数据类型最大的特点是唯一性,值通过函数生成在中对象的属性都是字符串,我们使用他人定义的对象,然后去新增自己的属性,这样容易起冲突覆盖原有的属性也可以看成为一个字符串,不过这个字符能保证是独一无二 20190125问: Es6中新增的数据类型有哪些?使用场景? es6中新增一种原始数据类型Symbol,最大的特点是唯一性,Symbol值通过S...

    jsummer 评论0 收藏0
  • JS每日题:Es6中新增的数据类型有哪些?使用场景?

    摘要:问中新增的数据类型有哪些使用场景中新增一种原始数据类型最大的特点是唯一性,值通过函数生成在中对象的属性都是字符串,我们使用他人定义的对象,然后去新增自己的属性,这样容易起冲突覆盖原有的属性也可以看成为一个字符串,不过这个字符能保证是独一无二 20190125问: Es6中新增的数据类型有哪些?使用场景? es6中新增一种原始数据类型Symbol,最大的特点是唯一性,Symbol值通过S...

    DataPipeline 评论0 收藏0
  • Javascript中Array方法的总结

    摘要:新建数组新建数组的方法有三种方法一方法二方法三新增是中新增的将一组值转换为数组的方法,该方法的出现时为了弥补构造函数因为参数不同导致的不同行为。 原文链接:http://mrzhang123.github.io/2016/08/03/js-Array 在ECMAScript中最常用的类型之一就是Array类型,Array类型的方法也有很多,所以在这篇文章中,梳理一下Array类型的方法...

    tracymac7 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<