一. 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 遍历的区别?
- for 循环支持 break 和 continue,而 forEach 不支持。
- 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: []})