js数组中一些实用的方法(forEach,map,filter,find)

前言

您将在本文中学习到

for循环与forEach/map/filter/find的一个使用对比

同for循环性能的一个比较

是不是一提到循环,就条件反射的只知道for循环呢,那么本文就是你想要知道的


1579435060211.png

需求场景: 假若后端返回这么一个json数据格式,如下所示,需要拿到返回对象中的数组项,或者根据某些指定的条件,取特定的值,然后渲染到页面当中去,例如拿name属性值

{
   "ret":true,
    "data":{
       "headerTitle":"群成员",
       "members":{
          "A":[
           {"id":"0A1","name":"小爱","imgPhoto","http://default.png","phoneNumber":1333344,“age”:18
            },
         ],
          "B":[],
          "C":[],
          "D":[
            {"id":"0D1","name":"小兵","imgPhoto","http://default.png","phoneNumber":14343344,"age":19
            },
         ],
      "E":[],
      "F":[],
      "H":[
        {"id":"0H1","name":"黄药师","imgPhoto","http://default.png","phoneNumber":14343344
        },],
      "L":[
          {"id":"0L1","name":"小 
  L1","imgPhoto","http://default.png","phoneNumber":14343344,"age":20},
          {"id":"0L2","name":"小L1","imgPhoto","http://default.png","phoneNumber":14343344,"age":22}
          {"id":"0L3","name":"小L2","imgPhoto","http://default.png","phoneNumber":14343344,"age":21}
  ]
  }
  }
}

对应的UI图

后台数据返回给前台,渲染UI效果.jpg

从数组对象中拿到特定的值渲染到页面当中,一些新增的方法就很有用了,单纯靠一个for循环就很难搞定了

目标:取对象中的值,然后循环遍历数组

Es5实现方法:先通过对象,方式拿到数组对象,然后for循环,拿到数组项

不同的框架代码中实现的方式语法表现有些不一样

Angular中

// array表示要遍历的数组,obj表示遍历时的每个元素,index表示遍历数组的下标,当然ng中提供 ng-repeat指令也是可以循环遍历
angular.forEach(array,funciton(obj,index){
  // dosomething
}

React中:

react中父组件向子组件传值,同样,使用最多是map方法

Vue中

vue中比较粗暴,直接用指令v-for="item in items"可以遍历,比较暴力
下面的是伪代码,仅供参考

var members = {}; // 初始化一个空对象,用于接收后台要返回的对象,然后通过DOM(innerHTML方式,字符串拼接)操作,将数据内填充到页面中指定的位置,当然下面的是伪代码模拟一下
axios.get("/api/mock/linker.json")
.then(res=>{
res = res.data;
if(res.ret == true){
let data = res.data;
this.members = data.members;
}
})
function showName(){
for(key in this.members){
  for(var i=0;i<members[key].length;i++){
    for(j in members[key]){
    console.log(members[key][j].name)
   }
      }
  }
}

}

如下一简单示例所示:从对象中取某一数组中的某个值,从对象中取某一数组中的某个值,与上面的数据格式是一致的

var obj = {
"data":{
"members": [
   {"id":111,"name":"小高"},
   {"id":222,"name":"小凡"},
   {"id":333,"name":"小王"}
]
}
}
var newArrs= []; // 初始化一个空数组
for( key in obj.data){
//console.log(obj.data[key]);
for(let i = 0;i<obj.data[key].length;i++){
console.log(obj.data[key][i]);
for(j in obj.data[key][i]){
console.log(obj.data[key][i].name);
newArrs.push(obj.data[key][i].name);
// break;至于为什么会重复打印两次,因为在里层for-in始终每次都得走一遍,然后在跳到上层for循环,加上break的话,就跳出for-in循环了的,就达到目的了,这里可以浏览器打断点看得出
}
}
}
console.log(newArrs);// (6) ["小高", "小高", "小凡", "小凡", "小王", "小王"]
console.log(new Set(newArrs)); // 去重,{"小高", "小凡", "小王"}
控制台上打印输出结果.jpg

js数组中的forEach实现:

var obj = {
"data":{
"members": [
{"id":111,"name":"小高"},
{"id":222,"name":"小凡"},
{"id":333,"name":"小王"}
]
}
}
var newArrs= [];
obj.data.members.forEach(function(member,index,originArrs){
newArrs.push(member.name);
})
console.log(newArrs); //["小高", "小凡", "小王"]

forEach

功能: 循环遍历数组中的每一项,只能遍历数组
写法:

数组对象.forEach(function(参数变量名1,参数变量名2,参数变量名3)){
// 做一些操作,forEach是没有返回值,返回值为undefined
}) 

特点:
callback函数,为数组中每个元素执行的函数,该函数接收三个参数

  • 变量参数名1表示的是数组中的项值(数组当前项的值)
  • 变量参数名2表示的是索引(数组当前项的索引)
  • 变量参数名3表示原数组(数组对象本身)
    返回值:undefined,并且总是返回undefined值,并且不可链式调用
    使用场景:
    邮箱,QQ列表,全选:删除所选项邮件等,todolist


    邮件中的列表.jpg

map

功能:循环遍历数组中的每一项,也只能遍历数组
写法:

数组对象.map(callback(参数名1,参数名2,参数名3){
 // 对原数组做一些操作
}

特点:
map使用方式与forEach类似,callback回调函数接收的参数意义与forEach一致
但是它必须有返回值,如果不给return,它会返回一个undefined
return的返回值是什么,相当于给这个新增的数组添加了新的值,但它不会影响原数据,只是将原来的数组拷贝了一份,把拷贝的数组项进行更改
使用场景:
拷贝原数组,改变一些东西,假定有一个数组(A),将A数组中的值以双倍的数值放到B数组中
Es5写法

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

for(var i = 0;i<numbersA.length;i++){
   numbersB.push(numbersA[i]*2);
}
console.log(numbersB)

Es6中map写法

ar numbersA = [1,2,3,4,5,6];
var numbersB = []
var numbersC = numbersA.map(function(numberA,index,originArrs){
    return numbersB.push(numberA*2);
}
console.log(numbersA); // [1,2,3,4,5,6]
console.log(numbersB);// []
console.log(numbersC);// [4, 8, 12, 16, 20, 24]
console.log(numbersC==numbersA)

​在一个数组对象中拿到数组中对象的属性

假定有一个对象数组(arrsA),将arrsA数组中对象某个属性的值存储到B数组中

var arrsA = [
             {name:"苹果",price:8888,city:"旧金山"},
             {name:"金立",price:1100,city:"深圳"},
              {name:"小米",price:999,city:"北京"},
              {name:"锤子",price:888,city:"上海"}
             ]
var sum = 0;
var prices = arrsA.map(function(item,index,arr){
         console.log(item,index,arr);
         return item.price;
});
console.log(prices); // [8888,1100,999,888]
prices.forEach(function(price,index,arr){
    sum += price;
});
console.log(sum);

filter

功能:
经过filter函数后会创建一个新的数组, 回调函数返回的结果一个boolean值,若结果为真,则返回匹配的项,若为假,则返回一个空数组,它不会改变原有数组,返回的是过滤后的新数组
写法:

数组对象.filter(function(currentVal,index,arrs){
    // 做一些操作
}

特点:filter函数遍历的元素范围在第一次调用回调函数callback的时候就已经确定了的,在调用filter之后添加到数组中的元素不会被filter遍历到,如果已经存在的元素被改变了,则他们传入callback的值是filter遍历到他们那一刻的值,被删除或从来未被赋值的元素不会被遍历到。
使用场景
示例1:假定有一个对象数组(A),获取数组中指定类型的对象放到B数组中

假定有一个对象数组(A,persons),获取数组A(这里指persons)中指定类型的对象放到B数组中

Es5实现
var persons = [
         {name:"小王",type:"boy",city:"广西",age:15,height:170},
         {name:"小美",type:"girl",city:"北京",age:16,height:180},
         {name:"小高",type:"girl",city:"湖南",age:18,height:175},
         {name:"小刘",type:"boy",city:"河北",age:20,height:177}
 ]
         // Es5,假定要拿person数组对象中类型为boy的对象
         var filterPersons = [];
         for(var i = 0;i< persons.length;i++){
             if (persons[i].type == "boy"){
                filterPersons.push(persons[i]); // 若是想要拿到对象的键值,直接persons[i].属性名
             }
         }
         console.log(filterPersons);

使用Es6中的filter实现
var persons = [
         {name:"小王",type:"boy",city:"广西",age:15,height:170},
         {name:"小美",type:"girl",city:"北京",age:16,height:180},
         {name:"小高",type:"girl",city:"湖南",age:18,height:175},
         {name:"小刘",type:"boy",city:"河北",age:20,height:177}
 ]

var filterPersons = persons.filter(function(person,index,arrs){
             return person.type === "boy";
         })
         console.log(filterPersons)

控制台显示如下所示


filter使用场景1.png

示例2:假定有一个数组(A,persons),过滤掉不满足以下条件的对象
取出persons数组对象满足类型为男孩,age大于18的,小于等于20,身高小于180,如果没有匹配的它会返回一个空数组

var persons = [
         {name:"小王",type:"boy",city:"广西",age:15,height:170},
         {name:"小美",type:"girl",city:"北京",age:16,height:180},
         {name:"小高",type:"girl",city:"湖南",age:18,height:175},
         {name:"小刘",type:"boy",city:"河北",age:20,height:177}
 ]
var filterNum = persons.filter(function(person,index,arrs){
    return person.type==="boy" && person.age > 18 && person.age<=20 && person.height<180
});
console.log(filterNum);// [{name: "小刘", type: "boy", city: "河北", age: 20, height: 177}]

示例3:​假定有两个对象(A),根据对象A中id值,过滤掉B数组中不符合的数据(也就是根据某个条件,去抽取出要操作对象中的属性)

var info =  {Id:4,content:"JavaScript"}

var languanges = [
        {Id:4,content:"Angular4"},
        {Id:2,content:"Vue.js",author:"尤大大"},
        {Id:3,content:"Node.js"},
        {Id:4,content:"React.js"}

]

var filterFun = function(info,languanges){
        return languanges.filter(function(laguange){
                     return laguange.Id === info.Id;
                })
            }
console.log(filterFun(info,languanges));//会挑选出languanges数组符合id等于4的选项,如果你想取对象中某个值,支持链式调用,直接跟着map或者forEach即可
如下链式调用
// 如下链式调用
var filterFun = function(info,languanges){
                return languanges.filter(function(laguange){
                     return laguange.Id === info.Id;
                }).map(function(currentVal){
                    return currentVal.content;
                }).forEach(function(curr){
                    console.log(curr);
                })
            }
console.log(filterFun(info,languanges)

这个filter方法有时会很有用,过滤掉某个对象中有没有某个属性值,有的话,就返回,没有就返回一个空数组,如下所示:找到满足laguanges数组对象中是否包含author的属性
var filterFun = function(info,languanges){
                return languanges.filter(function(laguange){
                     return laguange.hasOwnProperty("author");
                })
            }
console.log(filterFun(info,languanges))

find

功能:用来查找目标元素,若找到就返回该元素,若找不到就返回undefined,同样不会改变原有数组
写法:


数组.find(callback(参数1,参数2,参数3)

callback同样接受三个参数
第一个参数1表示的是当前遍历到的元素,
第二个参数2表示的是,每一次迭代查找的数组元素的索引
第三个参数3表示的是原操作数组

特点
找到第一个符合条件之后,就不会往后找了,这与filter过滤是不一样的,find方法比较快速便捷

返回值的是,若匿名回调函数结果为真,则返回所匹配的选项对象,若为假,则返回undefined

使用场景:
假定有一个数组对象(A),找到符合条件的对象

假定有一个对象数组(A),找到符合条件的对象,如下示例:找到learnWebs数组对象中name值为segementdefault

Es5实现

 var learnWebs = [
       {name:"segmentdefault"},
       {name:"MDN"},
       {name:"stackoverflow"},
       {name:"v2ex"},
       {name:"w3cplus"},
       {name:"segmentdefault"}
]
 var learnWeb = [];
 for(var i = 0;i < learnWebs.length; i++){
       if(learnWebs[i].name === "segmentdefault"){
        learnWeb.push(learnWebs[i]);
        //break;// 若不加break,都会走完一遍for循环
       }
 }
console.log(learnWeb);


使用Es6中的find实现
// Es6中的find方法,找到第一个符合条件之后的就不会往后在找了
var learnWebs = [
       {name:"segmentdefault"},
       {name:"MDN"},
       {name:"stackoverflow"},
       {name:"v2ex"},
       {name:"w3cplus"},
       {name:"segmentdefault"}
]

newWebs = learnWebs.find(function(learnWeb,index,orginArrs){
   return learnWeb.name ==="segmentdefault";
 })
 console.log(newWebs);

下面是find方法使用图解,只要迭代器函数中找到匹配项了,就不会往下找了,结果为真就会返回所匹配选项对象,若结果为假,则返回undefined
[图片上传失败...(image-25dce7-1580979324727)]

​示例2:假定有一个数组对象(A),根据指定对象的条件找到数组中符合条件的对象

假定有一个对象数组(A),根据指定对象的条件找到数组中符合条件的对象

例如:新闻列表,商品列表,博客文章等从商品列表数组对象中找到id,然后点击id跳转到详情页,从一个数组对象中找到对象中的某个id,进行匹配操作

var goods = [
    {id:1,name:"鞋子",size:34,color:"red"},
    {id:2,name:"皮包",type:"爱马仕"},
    {id:5,name:"手机",type:"iPhone6"}
]
var goodDetails = {id:5,desc:"我是手机详情页",price:666}

function showDetailFun(goods,goodDetails){
     return goods.find(function(good){
        return good.id===goodDetails.id;
     })
}
 console.log(showDetailFun(goods,goodDetails));


对应的UI界面,点击左边的商品列表页面,进入到商品对应的详情页(博客,新闻都与这类似)
>### 同for循环的性能比较

这是一个容易引起撕扯的话题,笔者准备不进行大篇幅的介绍,结论先行,从两个角度出发:

**性能上**:for循环>forEach>map

**可读性**: forEach/map>for循环

**区别**: for循环是按顺序遍历,按照下标索引的方式进行读取访问元素的,随机访问,而forEach/map等是使用iterator迭代器进行遍历,先取到数组中的每一项的地止放入到队列中,然后按顺序取出队里的地址来访问元素

大体上讲,如果数据量不是很大的情况下,抛开业务场景和使用便利性,单纯谈性能和效率是没有意义的,一些Es5,ES6新增的数组迭代器方法方便了前端开发,使得以往复杂或者冗长的代码,可以变得易读而且精炼

而好的for循环写法,在大数据量的情况下,确实也有着更好的兼容和多环境运行表现

你可以使用console.time()以及console.timeEnd()进行测试的,个人觉得还是要多用Es5和Es6新增的迭代器方法,相比于for循环,得定义初始值,得跟踪循环计数的变量,是很容易出问题的

代码是写给人看的,顺便在机器上运行。从这一角度上讲,个人支持多用Es5,Es6的迭代器方法,有时候在一些面试当中,会问你这其中的一些性能比较的

虽然有些无聊,并不是纠结你用哪个,但更多的是考验面试者的一个知识广度的

结语

以上的forEach,map,filter,find,方法都是不改变原有数组的,当然还有every,some等一些方法,forEach方法没有返回值,默认返回值为undefined,所以它不支持链式调用,而map,filter方法会返回一个新的数组

find方法返回的根据迭代器函数结果boolean值,若结果为真则返回指定的元素,若无则返回undefined

而改变原有数组的有:增加(push,unshift),删除(popshift),reverse(颠倒),sort(排序),splice,限于篇幅所致,以后也会拿出来分享的,针对数组中的这些方法是非常值得反复学习研究的,用好了,它就是神器

对于同for循环的性能的比较,其实没有多大的差异,还是要多用.

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

推荐阅读更多精彩内容