数组对象——一种特殊的对象
JS 其实没有真正的数组,只是用对象模拟数组
JS 的数组不是典型的数组
典型的数组
- 元素的数据类型相同
- 使用连续的内存存储
- 通过数字下标获取元素
但 JS 的数组不这样
- 元素的数据类型可以不同
- 内存不一定是连续的(对象是随机存储的 0
- 不能通过数字下标,而是通过字符串下标
- 这意味着数组可以有任何 key
- 比如
- let arr=[1,2,3]
- arr['xxx']=1
创建一个数组
新建
- let arr=[1,2,3]
- let arr=new Array(1,2,3)
- let arr=new Array(3)
转化
- let arr='1,2,3'.split(',')
- let arr='123'.split('')
- Array.from('123')
伪数组
- let divList=document.querySelectorAll('div')
- 伪数组的原型链中并没有数组的原型
- 可以通过 let divArray=Array.from(divList)转化
没有数组共用属性的[数组],就是伪数组
每次你拿到伪数组,要做的第一件事就是把它转化成数组
创建一个数组(续)
合并两个数组,得到新数组
- arr1.concat(arr2)
截取一个数组的一部分,不改变原来数组
- arr1.slice(1)//从第二个元素开始
- arr1.slice(0)//全部截取,常用来复制一个数组
- 注意,JS 只提供浅拷贝。所有 JS 原生提供的东西都是浅拷贝
增删改查——数组中的元素
删元素
跟对象一样
let arr=['a','b','c']- 'delete arr['0']
- arr//[empty,'b','c']
- 神奇,数组的长度并没有遍
- 只有长度,一个下标都没有,这种数组叫做稀疏数组。稀疏数组没有任何好处,它只有 bug。
- 实际上,这种删法是对象的删法,不太适合数组
如果直接改 length 可以删元素吗
let arr=[1,2,3,4,5];arr.length=1- 我 X,居然可以?!
- JS 真神奇
- 重要:不要随便改 length。改了不会报错,遇到 bug 你只能自己解决了。
删除头部的元素
- arr.shift()//arr 被修改,并返回被删元素
删除尾部的元素
- arr.pop()//arr 被修改,并返回被删元素
删除中间的元素
- arr.splice(index,1)//删除 index 的一个元素
- arr.splice(index,1,'x')//并在删除位置添加'x'
- arr.splice(index,1,'x','y')//并在删除位置添加'x','y'
查看所有元素
查看所有属性名
- let arr=[1,2,3,4,5];arr.x='xxx'
- Object.keys(arr)
- for(let key in arr){console.log(
${key}:${arr[key]})} - 上面的方法都只适合对象不适合数组
查看数字(字符串)属性名和值
for(let i=0;i<arr.length;i++){
console.log(`${i}:${i}`)
}
- 你要自己让 i 从 0 增长到 length-1。
- for 循环是访问数组的吧比较常见的形式,不要用 for in,for in 是用来访问对象的。
- 也不要用 Object.keys,它可能会获取到你不想要的东西
arr.forEach(function(item,index){
console.log(`${index}:${item}`)
})
- 也可以用 forEach/map 等原型上的函数
面试:请聊一聊 for 循环和 forEach 的区别。
第一个区别:上面两种大部分情况都是通用的,只有一种情况不通用,就是 for 循环里面有 break 和 continue 的时候,而 forEach 是不支持的,因为 forEach 是个普通的函数,而 for 循环是个关键字,关键字的功能可能会更强大一点。
第二个区别:for 循环是个关键字,不是个函数,所以 for 循环只有块级作用域,没有函数作用域。但是 forEach 是个函数,所以它里面是函数作用域。一个是块,一个是函数。
forEach 是一个槛
自己写 forEach 才能理解 forEach
function forEach(array,fn){
for(let i=0;i<array.length;i++){
fn(array[i],i,array)
}
}
- forEach 用 for 访问 array 的每一项
- 对每一项调用 fn(array[i],i,array)
- 为什么要传入 array 呢?不为什么,规定如此。
查看单个属性
跟对象一样
- let arr=[111,222,333]
- arr[0]
索引越界
- arr[arr.length]===undefined
- arr[-1]===undefined
举例
for(let i=0;i<=arr.length;i++){
console.log(arr[i].toString())
}
- 报错:Cannot read property 'toString' of undefined
- 任何不存在的下标你去读都会得到 undefined,undefined 不是一个对象,不是一个对象那么它就没有 toString 属性。当你看到上面这句报错的话,很有可能你是数组越界了
- 如何解决这个问题,用 console.log 调试大法,它说哪个东西是 undefined,你就把哪个东西打印出来
查找某个元素是否在数组里
- arr.indexOf(item) // 存在返回索引,不存在则返回-1
使用条件查找元素
arr.find(item=>item%2===0)//找第一个偶数- 可以找到符合这个条件的第一个元素
使用条件查找元素的索引
arr.findIndex(item=>item%2===0)// 找第一个偶数的索引- 可以找到符合这个条件的第一个元素的索引
增加数组中的元素
在尾部增加元素
- arr.push(newItem) // 修改 arr,返回新长度
- arr.push(item1,item2) // 修改 arr,返回新长度
在头部增加元素
- arr.unshift(newItem) // 修改 arr,返回新长度
- arr.unshift(item1,item2) // 修改 arr,返回新长度
在中间添加元素
- arr.splice(index,0,'x') //在 index 处插入'x'
- arr.splice(index,0,'x','y') //在 index 处插入'x','y'或者更多元素
修改数组中的元素
反转顺序
- arr.reverse() // 修改原数组
- 反转一个字符串:`s.split('').reverse().join('')
自定义顺序
- arr.sort((a,b)=>a-b)
- 例:
let arr=[
{name:'小明',score:99},{name:'小红',score:95},{name:'大黄',score:100}
]
arr.sort((a,b)=>a.score-b.score)
数组变换
- 此三个函数都会返回一个新数组,不改变原数组
map:n 变 n
map 的意思是一一映射
let arr=[1,2,3,4,5,6]
arr.map(item=>item*item) //把arr的每一项一一映射,变成它的平方
filter:n 变少
let arr=[1,2,3,4,5,6]
arr.filter(item=>item%2===0?true:false)
// 返回偶数。
简写:
let arr=[1,2,3,4,5,6]
arr.filter(item=>item%2===0)
reduce:n 变 1
let arr=[1,2,3,4,5,6]
arr.reduce((sum,item)=>{return sum+item},0)//return的值作为下一次的结果
把 reduce 当 map 用
let arr=[1,2,3,4,5,6]
arr.reduce((result,item)=>{return result.concat(item*item),[]})
//返回一个新数组,每一个元素是原来数组元素的平方
把 reduce 当 filter 用
let arr=[1,2,3,4,5,6]
arr.reduce((result,item)=>item%2===1?result:result.concat(item))
上面可以简写:
let arr=[1,2,3,4,5,6]
arr.reduce((result,item)=>
result.concat(item%2===1?[]:item)
,[])
- reduce 是数组里功能最强大的 API,有了 reduce 你不需要 map,map 只是一个语法糖而已