JavaScript 数组

408 阅读5分钟

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

在 JS 中,数组不是数据类型,它属于 object 对象,是一种特殊的对象。JS 其实没有真正的数组,只是用对象模拟数组。

典型的数组

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

JS 的数组

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

二. Array.isArray()

Array.isArray() 用于确定传递的值是否是一个Array,如果值是Array,返回true,否则返回false

三. 创建一个数组

1. 新建

let arr = [1, 2, 3]
let arr = new Array(1, 2, 3)  //元素为1,2,3
let arr = new Array(3)        //长度为3

2. 转化

split方法使用指定的分隔符将一个字符串分割成一个字符串数组。

let arr = '1,2,3'.split(',')  //逗号分隔
let arr2 = '1 2 3'.split(' ') //空格字符串分隔
arr   //["1", "2", "3"]
arr2  //["1", "2", "3"]

Array.from()能把字符串转化为数组,如果一个对象有如 0, 1, 2, 3 这样的下标,且有 length 属性,也能转化为数组。如果下标个数和 length 值不一致,得到的数组长度等于 length 值。

Array.from('123')  //["1", "2", "3"]
Array.from({0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4})  //["a", "b", "c", "d"]
Array.from({0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 2})  //["a", "b"]

3. 伪数组

没有数组的共用属性的数组就是伪数组,伪数组的原型链中没有数组的原型。

let array = {0: 'a',1: 'b',2: 'c',3: 'd',length: 4} 
array.constructor  //原型为 Object.prototype

4. 合并两个数组,得到新数组concat

let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
arr1.concat(arr2) //[1, 2, 3, 4, 5, 6]

5. 截取数组的一部分slice

let arr =  [1, 2, 3, 4, 5, 6]
arr.slice(1)   //从第二个元素开始截取,[2, 3, 4, 5, 6] 
arr.slice(0)   //全部截取,[1, 2, 3, 4, 5, 6] 

slice 常用于复制数组,注意JS 只提供浅拷贝

四. 数组元素的增删改查

1. 删除元素

不推荐的两种方法

(1) delete arr['xxx'](跟对象一样)

let arr = ['a', 'b', 'c']
delete arr['0']  //true
arr  //[empty, "b", "c"]
delete arr[1]
delete arr[2]
arr //[empty × 3],没有下标,只剩长度

上面代码中,使用 delete 操作后的 arr 的长度并没有变,像这样只有长度但是没有对应的下标的数组叫稀疏数组(没有任何好处,只有bug)

(2) 直接改 length

let arr = [1, 2, 3, 4, 5] 
arr2.length = 2
arr2  //[1, 2]

不要随便改 length,很容易出 bug。

删除元素的正确方式

(1) 删除头部的元素

arr.shift()  //arr被修改,并返回被删元素,length 会相应变化

(2) 删除尾部的元素

arr.pop()  //arr 被修改,并返回被删元素,length 会相应变化

(3) 删除中间的元素

arr.splice(index, 1)  //删除下标为 index 的一个元素,并返回被删元素
arr.splice(index, 1, 'x')      //删除 index 的一个元素,并在删除位置添加'x'
arr.splice(index, 1, 'x', 'y') //删除 index 的一个元素,并在删除位置添加'x','y'

2. 查看元素

(1) 查看所有元素

  • 使用 for 循环遍历数组
let arr = ['a', 'b', 'c', 'd', 'e']
for (let i = 0; i < arr.length; i++) {
  console.log(`${i}: ${arr[i]}`)
}
  • 使用 forEach 遍历数组
let arr = ['a', 'b', 'c', 'd', 'e']
arr.forEach(function(item, index) {
  console.log(`${index}: ${item}`)
})

forEach 方法接受一个函数,该函数可以遍历每一项。forEach 函数的原理如下:

function forEach(array, fn) {
  for (let i = 0; i < array.length; i++) {
    fn(array[i], i)  //回调
  }
}
forEach(['a', 'b', 'c'], function(x, y) {
  console.log(x, y)
})
//a 0
//b 1
//c 2
  • 面试题:使用 for 循环和 forEach 遍历的区别?
  1. for 循环支持 break 和 continue,而 forEach 不支持。
  2. for 是关键字,没有函数作用域,只有块级作用域,而 forEach 是函数,只有函数作用域。

(2) 查看单个元素

  • 跟对象一样
let arr = [1, 2, 3]
arr[1]  //数字1会自动变成字符串'1'
  • 索引越界 索引不存在就是索引越界,读取任何不存在的下标都会得到 undefined
let arr = [1, 2, 3, 4, 5, 6, 7, 8]

arr[arr.length] === undefined //true
arr[-1] === undefined         //true

for (let i = 0; i <= arr.length; i++) {
  console.log(arr[i].toString())
}  //TypeError: Cannot read property 'toString' of undefined

报错:Cannot read property 'toString' of undefined 意思是读取了 undefined 的 toString 属性,而不是 toString 是 undefined,比如 x.toString() 其中 x 如果是 undefined 就会报这个错。

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

arr.indexOf(item) 存在返回索引,否则返回-1

let arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr.indexOf(10)  //-1
arr.indexOf(5)   //4

(4) 使用条件查找元素

arr.find(item => item % 2 === 0)  //查找第一个偶数,返回第一个符合条件的元素

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

arr.findIndex(item => item % 2 === 0)  //查找并返回第一个偶数的索引

3. 增加元素

(1) 在尾部加元素

arr.push(newItem)      //修改 arr,返回新长度
arr.push(item1, item2) //修改 arr,返回新长度

(2) 在头部加元素

arr.unshift(newItem)       //修改 arr,返回新长度
arr.unshift(item1, item2)  //修改 arr,返回新长度

(3) 在中间添加元素

arr.splice(index, 0, 'x')      //在 index 处插入 'x'
arr.splice(index, 0, 'x', 'y') //在 index 处插入 'x','y'

4. 修改元素

(1) 使用 splice 或直接修改

let arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr.splice(5, 1, 666)
arr  //[1, 2, 3, 4, 5, 666, 7, 8]
arr[6] = 777
arr  //[1, 2, 3, 4, 5, 666, 777, 8]

(2) 反转顺序

arr.reverse()  //会修改原数组

面试题:将字符串 'abcde' 变成 'edcba'

let str = 'abcde'
str.split('')    //["a", "b", "c", "d", "e"]
str.split('').reverse()    //["e", "d", "c", "b", "a"]
str.split('').reverse().join('')    //"edcba"

(3) 自定义顺序

arr.sort((a, b) => a-b)
arr.sort((a, b) => b-a)

示例:

let arr = [5, 4, 3, 2, 1]
arr.sort((a, b) => a - b)  //[1, 2, 3, 4, 5]
arr.sort((a, b) => b - a)  //[5, 4, 3, 2, 1]

四. 数组变换

map n变n

let arr = [1, 2, 3, 4, 5, 6]
arr.map((item) => item * item)  //[1, 4, 9, 16, 25, 36]

filter n变少

let arr = [1, 2, 3, 4, 5, 6]
arr.filter(item => item % 2 === 0)  //[2, 4, 6]

reduce n变1

reduce()方法的返回值为函数累计处理的结果。

let arr = [1, 2, 3, 4, 5, 6]

//求和
arr.reduce((sum, item) => {
    return sum + item
  }, 0)  //21
  
//平方
arr.reduce((result, item) => {
    return result.concat(item * item)
  }, [])  //[1, 4, 9, 16, 25, 36]

//偶数
arr.reduce((result, item) => result.concat(item % 2 === 1 ? [] : item), [])  //[2, 4, 6]

面试题:数据变换

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}, 
  ]
}

答案:使用 reduce

let arr = [
  { 名称: '动物', id: 1, parent: null}, 
  { 名称: '狗', id: 2, parent: 1},
  { 名称: '猫', id: 3, parent: 1}
]
arr.reduce((result, item) => {
  if (item.parent === null) {
    result.id = item.id
    result['名称'] = item['名称']
  } else {
    result.children.push(item)
    delete item.parent
    item.children = null
  }
  return result
}, {id: null,children: []})