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

前言

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

前端配图

新增方法预览

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

  1. forEach
  2. map
  3. filter
  4. some
  5. every
  6. indexOf
  7. lastIndexOf
  8. reduce
  9. 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地址

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

博客地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容