【JS】数组

271 阅读11分钟

一、JS的数组不是典型数组

1.典型数组

  • 元素的数据类型相同
  • 使用连续的内存存储
  • 通过数字下标获取元素

2.JS数组(JS数组是个对象)

  • 元素的数据类型可以不同
  • 内存不一定是连续的(对象是随机存储的)
  • 不能通过数组下标,而是通过字符串下标
  • 这以为着数组可以有任何key
  • 比如let arr = [1,2,3];arr['xxx'] =1

(1)下面将用let arr = [1,obj,3]举例

(2)JS数组是个对象!对象有属性和属性值

(3)关于元素(item)

  • 数组中的一个个元素1obj3其实是一个个属性值
  • 而属性值的数据类型可以各种各样.也就是元素的数据类型多种多样,可以不相同

(4)关于下标(index)

  • 下标其实是这些属性值的属性名,是一个个字符串数字。
  • 比如规定了"0"是一个属性名,表示第一个元素1的下标。
  • 规定"1"是一个属性名,在数组中规定为表示第二个元素obj的下标

(5)关于储存

  • 因为JS数组是对象,所以JS数组在内存中储存在heap堆中,是随机储存的

(6)因为JS数组是对象,所以JS数组还可以有任何属性。

  • 比如arr['xxx']= 'yyy'就给这个数组添加了一个属性'xxx',属性值是一个字符串'yyy'

二、 创建数组

1.新建

  • let arr = [1,2,3] 简易写法
  • let arr = new Array(1,2,3) 完整写法
  • let arr = new Arrat(3) '3' 指的是字符串长度

2.转化(用字符串生成一个新数组)

(1)字符串的一个APIsplit
  • stringObject.split(separator,howmany)
  • separator表示选择字符串中哪个字符作为分隔
  • howmany可选。该参数可指定返回的数组的最大长度
  • 如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
  • 可以让这个字符串生成一个数组。
  • 原字符串不变,只是借助这个字符串生成了一个新的对应的数组。
let str = '1,2,3'
str.split(',')  
//它是一个数组,选择用逗号来分隔这个字符串。数组中每个元素都是一个字符。
let str2= '123'
str.split('')
//它是一个数组,因为用空字符串来分隔这个字符串。字符和字符之间就会被空字符串分开了,这时便会成为一个数组。数组中每个元素都是一个字符。
//原字符串不变

(2)Array from('123')
  • 可以把不是数组的东西变为数组
  • 比如把字符串变成数组 注意:Array.from不是能把所有东西都变成数组,要求:
  • 1.对象有下标
  • 2.有length属性

3.合并两个数组,得到一个新数组

  • 数组1.concat(数组2) 举例:arr1.concat(arr2)
  • 原来的两个数组不会改变,只是得到一个新数组
  • 注意数组2其实也可以为一个值

4.截取一个数组的一部分成为一个新数组

  • 数组名.slice(index)从第index下标开始截取后面的元素为一个新数组
  • 数组名.slice(0)从第0个下标也就是第一个元素开始截取。意思就是把这整个数组复制下来成为一个新数组。
  • 原数组不变

5.伪数组

  • 真数组的第一层原型为Array.prototype,第二层原型为Object.prototype
  • 伪数组的原型链中没有数组的原型Array.prototype,所以伪数组没有真数组该有的共有属性。
  • 只是伪数组恰好有属性'length'和属性名'0','1','2'才让他看起来像真数组。
  • 比如这个对象{0:'yy',1:'18',2:3,length:3}就是伪数组,他的原型就为Object.prototype
  • 比如let divList = document.querySelectorAll('div')是个伪数组
  • 可以用Array.from()把伪数组变为数组

三、数组的增删查改

(一).删元素

跟对象一样

let arr = ['a','b','c']
delete arr['0]
arr //返回 [empty,'b','c'],十分奇葩,含有empty的数组叫稀疏数组,有bug,不要用

如果直接改length,可以删元素吗?

let arr = [1,2,3,4,5]
arr.length = 1
arr // [1] ,修改length会删除元素,不要随便改length

删除元素的推荐API( 对象提供的函数,就叫做API)

arr.shift() //删除第一个
arr.pop() //删除最后一个
arr.splice(index,1) //删除从第index起的第一个
arr.splice(index,1,'x') //删除从第index起的第一个,并从第index位起添加'x'
arr.splice(index,1,'x','y') //删除从第index起的第一个,并从第index位起依次添加'x','y'

(二)查看元素

1、查看所有元素

(1)不推荐方法(更适合对象)

  • Object.keys(obj)
  • for (let key in arr){console.log('${key}:${arr[key]}')

(2)推荐方法

①用for循环遍历数组

for(let i = 0; i < arr.length; i++){
    console.log(`${i}: ${arr[i]}`)
}

你要自己让i从0增长到length-1

②用数组原型上带有的forEach函数

  • forEach函数作用是遍历数组,每一次都调用新写的这个函数
  • 所以把这个函数写为打印出元素和下标,就可以在遍历数组的时候打印出每个元素与下标了
arr.forEach( function(item,index){
console.log(`${index}: ${item}`)
})

理解:我们自己写个forEach函数才可以理解

function forEach(array,fn){ //创建一个函数forEach,这个函数有两个参数;一个数组和一个函数fn
    for(let i = 0; i < array.length; i++){  //forEach函数的操作是让i从0循环到数组长度-1
        fn(array[i],i,array)  //循环体为把数组的第i个元素、下标i、整个数组当做fn的参数,然后执行函数fn
    }
}


// 所以自带的forEach函数就是自带遍历数组功能for,比我们少写了一句for

举例:

③两者区别

  • 当for循环中有continue或break时两者不一样
  • for循环可以在某一位置终止
for(let i=0;i<arr.length;i++){
console.log('${i} : ${arr[i]}')
if (i===3){break;}
}
//在i循环到3是跳出循环。也就是只打印出前四个元素就退出循环
  • forEach只能从头遍历到尾
  • for循环是块级作用域
  • forEach是函数作用域
2、查看单个元素

(1)只可以用中括号法

let arr=[111,222,333]
arr[0] // 返回111

(2)索引越界

  • 定义: 数组索引越界是指arr['index']中的下标index应该取0arr.length-1,但是如果取得下标不是这个范围,那arr['index']就不存在,这就叫索引越界,这时arr[index]===undefined
for(let i=0;i<=arr.length;i++){
    console.log(arr[i].toString())
}
//报错:Cannot read property 'toString' of undefined

理解:因为i只能取0到arr.length-1,而上面代码把i取到了arr.length,然而arr[arr.length]不存在啊。所以arr[arr.length]===undefined,所以一个undefined当然没有toString函数。只有对象才有toString函数

(3)查看某个元素是否在数组里

  • arr.indexOf(item) 存在则返回item的下标,否则返回

(4)使用条件查找元素

  • arr.find(function(x){return x%5===0}) 找第一个五的倍数,然后返回那个元素值,但是无法返回元素的下标
  • arr.find(item => item %2 === 0)

(5)使用条件查找元素的索引

  • arr.findIndex(function(x){return x%5===0}) 返回第一个五的倍数的元素的下标
  • arr.findIndex(item => item %2 === 0)
  • 举例:
arr = [12,23,12,222,45,16]
arr.find(function(x){return x%5===0})
//find函数遍历每个元素,把元素给自写函数当参数
//自写函数是以元素为参数,如果出现了一个元素是5的倍数则返回true。
//这时find函数就会返回这个元素45
arr.findIndex(function(x){return x%5===0})
//返回元素下标4

(三)增加元素

(1)在尾部加元素

  • 数组名.push(newitem)
  • 数组名.push(newitem1,newitem2)
  • 修改了该数组,并且返回新长度

(2)在头部加元素

  • 数组名.unshift(newitem)
  • 数组名.unshift(newitem1,newitem2)
  • 修改了该数组,并且返回新长度

(3)在中间加元素

  • 数组名.splice(index,0,'x','y')
  • 从index下标开始,啥也不删,再加上这个元素
  • 修改了该数组,并且返回新长度

(四)修改元素

1.arr.splice(index,1,'x') 将第index+1的位置元素替换为'x' 2.arr[4] = 4 将第五个元素替换为4 3.arr.reverse() 将数组反转,修改原数组 举例:

let s = 'abcde'
s.split('')   //["a", "b", "c", "d", "e"] 把字符串分开成数组
s.split('').reverse()  //["e", "d", "c", "b", "a"] 把数组反转顺序
s.split('').reverse().join('')  //"edcba" 把数组合成字符串

4.自定义顺序

  • 我们只管自己写个函数判断数组中元素a、b的大小,然后必须返回1,0,-1,不用管排序。
  • sort函数会根据返回的1,0,-1来排列元素
  • sort函数默认把元素从小到大排列,但是谁大谁小sort函数不知道,他必须通过我们返回的1,0,-1来判断
  • sort函数认为
  • 1表示最后算a大,往后排
  • -1表示最后算a小,往前排
  • 0表示a、b元素一样大

例1

arr = [1,8,2,6,4]
arr.sort(function(a,b){
    if(a>b) {return 1} //a比b大,就算a大,排后面
    else if(a===b) {return 0} //a和b一样大,就算两者一样的
    else {return -1} // a比b小,就算a小,排前面
})  //[1, 2, 4, 6, 8]//可以简写为
arr.sort((a,b)=>{return a-b})
//可以简写为
arr.sort((a,b)=> a-b) //谁大就大,谁小就小

例2

arr = [1,8,2,6,4]
arr.sort(function(a,b){
    if(a>b) {return -1} //a比b大,最后就算a小,排前面
    else if(a===b) {return 0} //a和b一样大,就算两者一样的
    else return {1} // a比b小,最后就算a大,排后面
}) //[8, 6, 4, 2, 1]//可以简写为
arr.sort((a,b)=>{return b-a})
//可以简写为
arr.sort((a,b)=>b-a)  //谁大就小,谁小就大

例3

let arr= [
  {'name':'小明','score':99},
  {'name':'小百','score':76},
  {'name':'小旅','score':82}
]
//我们想根据成绩排名
arr.sort(function(a,b){
    if(a.score>b.score){return -1}  //a的分数比b的分数大,最后就算a小,排前面
     else if(a.score===b.score) {return 0} //a的分数和b的分数一样大,就算两者一样的
    else {return 1} // a的分数比b的分数小,最后就算a大,排后面
})
/* 0: {name: "小明", score: 99}
   1: {name: "小旅", score: 82}
   2: {name: "小百", score: 76}  *///可以简写为
arr.sort((a,b)=>{return b.score-a.score})
//可以简写为
arr.sort((a,b)=>b.score-a.score)  //谁大就小,谁小就大

sort函数规定了

  • 我得到正数,说明a大,往后排
  • 我得到负数,说明a小,往前排
  • 我得到0,说明一样大

我们呢

  • 为了让谁大就是真大,然后就可以排在后面,那就a-b
  • 为了让谁大其实是小,然后就可以排在前面,那就b-a
  • b-a为正数,b大,但是sort认为a大,b小
  • b-a为负数,b小,但是sort认为b大,a小

四、数组变换:返回新数组,元素组不变

1、map:n变n,一一映射

  • map是数组的一个API
  • map(fn)是个函数,它的参数是一个我们自己写的函数fn(和自身数组),他会遍历数组中每个元素,把元素给fn当实际参数。最后返回新数组
  • 注意fn以元素为实际参数,返回对元素的某种操作,然后filter会把《fn返回的东西》组成一个新数组
//把数组中每个元素都变成平方,组成一个新数组
arr=[1,2,3]

//方法一:可以用for循环实现,但是麻烦不要
for(let i =0;i<arr.length;i++){
    arr[i]=arr[i]*arr[i]
}//不对啊,这样写得不到新数组,而是修改了元素组
arr1 = []
for(let i =0;i<arr.length;i++){
    arr1.push(arr[i]*arr[i])
}
arr1 //[1, 4, 9]

//方法二:用map,一句搞定
arr.map(item=>{return item*item}) //[1, 4, 9]

//方法三:用reduce,那map就可以再见了
//结果会是个数组,那结果的初始值为一个空数组
//我们想把元素的平方和结果(数组)相连得到新结果(数组)
arr.reduce((result,item)=>{return result.concat(item*item) },[])   //[1, 4, 9]
把arr变成对应的星期
let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr2 = arr.map((item)=>{
  let week =['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  return week[item]
})
console.log(arr2) // ['周日', '周一', '周二', '周二', '周三', '周三', '周三', '周四', '周四', '周四', '周四','周六']


let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr2 = arr.map((i)=>{
  return {0:'周日',1:'周一',2:'周二',3:'周三',4:'周四',5:'周五',6:'周六'}[i]
})
console.log(arr2)

2、(二)filter:n变少

  • filter是数组的一个API
  • filter(fn)是个函数,它的参数是一个我们自己写的函数fn(和自身数组),他会遍历数组中每个元素,把元素给fn当实际参数。
  • 注意fn以元素为实际参数,需要返回布尔值,然后filter会把《fn中返回true的元素》组成一个新数组
//把原数组中的偶数元素组成新数组
arr = [1,2,3,4,5,6]
//方法一:用filter
arr.filter(item => item % 2===0)  //[2, 4, 6]

//方法二:用reduce,那filter就可以再见了
arr.reduce((result,item)=>{  
    if(item%2===1){return result} //如果元素是奇数,新结果为原来的结果
    else{ return result.concat(item)}   //如果元素是偶数,新结果为原来的结果连上该元素
},[])  //结果的初始值为空数组
//[2, 4, 6]

//方法二代码优化
arr.reduce((result,item)=>{  
     return item%2===1? result :result.concat(item)
},[])

//方法二再代码优化
arr.reduce((result,item)=>{  
   return result.concat(item%2===1?[]:item)
},[])
let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr2 = arr.map(item=>{
if(item===0){return item="周日"}
else if(item===1){return item="周一"}
  else if(item===2){return item="周二"}
  else if(item===3){return item="周三"}
  else if(item===4){return item="周四"}
  else{return item="周六"}
})
console.log(arr2) // ['周日', '周一', '周二', '周二', '周三', '周三', '周三', '周四', '周四', '周四', '周四','周六']

3、reduce:n变1,功能最强大

  • reduce(函数,结果的初始值),还有个隐藏参数就是自身数组,他会遍历数组中每个元素。每次都执行函数
  • 函数以结果和元素为参数,我们想把元素如何作用于结果,然后会返回一个新结果。新结果再参与下次循环
  • 最终reduce函数的返回一个最新的结果
对数组中所有元素求和
arr=[1,2,3,4,5,6]
//方法一:用for循环
sum = 0  //结果是个数字,初始值为0
for(var i =0;i<arr.length;i++){
   sum = sum+arr[i]    //我们想结果+元素得到新结果
}
sum //27

//方法二:用filter
arr.reduce((sum,item)=>{return sum+item},0)
求奇数之和
let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
let sum = scores.reduce((sum, n)=> n%2===0?sum:sum+n,0)

面试题

//题目
let arr = [
{名称:'动物', id: 1, parent: null},
{名称:'狗', id: 2, parent: 1}, 
{名称:'猫', id: 3, parent: 1}
]
      //把数组变成对象
{
id: 1, 名称:'动物', children: [
{id: 2, 名称:'狗', children: null},
{id: 3, 名称:'猫', children: null},
] }

//解答
arr.reduce(
  (result,item)=>{
    if(item.parent===null){      //通过元素的parent属性把元素分成两类
      result.id=item.id 
      result['名称']=item['名称']
      }
    else{
      delete item.parent
      item['children']=null
      result.children.push(item) //往children的数组里面推送元素
      }
    return result
    }
,{children:[]})  //结果的初始值是一个对象,对象中有个属性children,属性值是个空数组