前言:JS其实没有真正的数组,它只是用对象模拟数组
JS的数组不是典型数组
典型的数组
- 元素的数据相同
- 使用连续的内存存储
- 通过数字下标获取元素
JS的数组
- 元素的数据类型可以不同
- 内存不一定是连续的(因为对象是随机存储的)
- 不是通过数字下标,而是通过字符串下标获取元素(写的数字会自动转变成字符串)
- 如:
- array[1] 中的1不是字符串,但JS会自动调用toString使之变为array[(1).toString]的形式,再执行
- 这意为着数组可以有任意key
- 比如
let arr = [1,2,3]arr['xxx'] = 1- 这在JS中也是合法的
创建一个数组
新建
let arr = [1,2,3]let arr = new Array(1,2,3)let arr = new Array(3)这里的3是指数组的长度
转化
let arr = '1,2,3'.split(',')let arr = '123'.split('')Array.from('123')- 把不是数组的东西尝试变成数组
- 条件1:这个对象有0,1,2,3这样的下标
- 条件2:有一个length属性
- 注:length的长度小于应有长度,会被自动截取
- Array.from 可以把伪数组转换成数组
伪数组
伪数组的原型链中并没有数组的原型,所以它没有常见的数组操作方法
如图,我们定义一个数组
它的原型链中有数组的原型 [[Prototype]]:Array(0)也就是__proto__:Array(0)它的数组的独有的,也就数组的精髓,而在它之中才包含了普通公共的原型__proto__:Object
而伪数组并没有这样的原型
一个典型的伪数组
let divList = document.querySelectorAll('div')
这段代码创建的数组就是一个伪数组
可以看到它没有数组原型
[[Prototype]]:Array(0),只有[[Prototype]]:NodeList
那怎么把它转化成数组?
很简单,使用Array.from即可
let divArray = Array.from(divList)
合并两个数组,得到新数组
arr1.concat(arr2)- 不会改变原来的两个数组
截取一个数组的一部分
arr.slice(1)从数组下标为1的元素开始截取,原数组不变arr.slice(1,4)从数组下标为1的元素开始截取到下标为4-1的元素arr1.slice(0)全部截取- 其意义就是复制一个数组(因为JS不提供数组复制的方法)
注意:JS只提供浅拷贝
JS数组的增删改查
删元素
如果我们用删对象的方法删除
得到的数组会出现'empty',并且数组本身的长度不会改变
像这样数组中有'empty'的数组,称为稀疏数组,稀疏数组没有任何好处,只有BUG
所以不推荐这样的删法
如果直接改length呢?
可以实现删除,但是这样的方法也不是常规的删除方式,可能会出现BUG,所以也不推荐
正确的删除方法如下:
删除头部元素
arr.shift()- arr被修改,并返回被删元素,同时长度也会改变
删除尾部元素
arr.pop()- arr被修改,并返回被删元素,同时长度也会改变
删除中间元素
arr.splice(index,1)删除下标为index的这一个元素arr.splice(index,3)删除下标为index开始的三个元素arr.splice(index,1,'x')删除下标为index的元素,并在原本的位置上添加x(可添加多个)
查看元素
同样地,不推荐使用查看对象的方式查看数组
我们可以用如下两个方法
查看数组的属性名和值
- for 循环打印
for(let i = 0; i < arr.length; i++){
console.log(`${i}:${arr[i]}`)
}
- forEach 函数
arr.forEach(function(item,index){
console.log(`${index}:${item}`)
})
怎样理解forEach?
我们可以手写一个forEach
function forEach(array, fn) {
for(let i = 0; i < arr.length; i++){
fn(array[i], i, array)
}
}
forEach(['a','b','c'],function(x,y,z){console.log(x,y,z)})
得到如下结果:
分别打印出了数组的值,下标,和整个数组(通常省略不用)
所以foreach的本质就是用for循环遍历array的每一项,并对每一项都调用fn(array[i], i, array)
for循环和forEach有什么不同?
for是块,forEach是函数,
两者基本相同,一种情况不同:
for里可以有break和continue
如图,我们可以用for加break只遍历到数组的第四个元素
查看单个属性
arr[index]查看下标为index的元素 注意索引越界- arr[arr.length]
- arr[-1]
- 两者都会返回 undefined
- 因为没有index为-1和arr.length的值
索引越界例子:
出现报错,意为你读取了undefined的toString属性,它不能被读取
原因便是i<=arr.length处,这里不应该有等于号,因为没有下标会等于字符串长度
arr.indexOf(item)查找某个元素是否在数组里,存在返回索引,否则返回-1arr.find(条件)使用条件查找元素- 如:
arr.find(item=>item%2 === 0))可以找到数组中第一个偶数 - 注意只会返回第一个
- 如:
arr.findIndex(条件)使用条件查找元素的索引- 注意只会返回第一个
增加数组中的元素
同样不推荐用对象的方法直接写,因为容易使数组变成稀疏数组
在尾部加元素
arr.push(newItem)修改arr,返回新长度arr.push(item1,item2)添加多个
在头部加元素
arr.unshift(newItem)修改arr,返回新长度arr.unshift(item1,item2)添加多个
在中间添加元素
arr.splice(index,0,'x','y')在index后面插入'x','y',0表示一个元素也不删除
修改数组中的元素
很简单,我们可以直接用索引直接改
arr[idex]=x
数组排序
反转顺序
arr.reverse()会修改元素组 如何反转一个字符串?
将字符串转换为数组,反转之后再转回字符串
自定义顺序
(默认是从小到大排序)
我们有一个函数 arr.sort(function(a,b){return 1/0/-1})
如果给sort返回1(正数):左边大于右边
返回0:一样大
返回-1(负数):右边大于左边
知道这个原理就可以构造出一个排序:
然而我们知道sort是通过返回值的正负来判断顺序,所以上述的sort就可以简化为
arr.sort((a,b)=>a-b)
用这个方法来给成绩排序:
数组变换
注:数组变换都不会改变原数组
map n变n
例:把数字变成星期
let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr2 = arr.map(补全代码)
console.log(arr2) // ['周日', '周一', '周二', '周二', '周三', '周三', '周三', '周四', '周四', '周四', '周四','周六']
当然,我们可以用if…else来进行判断,但还有一个很加简洁的方法:
let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
let arr2 = arr.map((i)=>{
const hash = {0:'周日',1:'周一',2:'周二',3:'周三',4:'周四',5:'周五',6:'周六'}
return hash[i]
})
直接将属性名和属性值返回到一个数组中即可
filter n变少
例:找出所有大于60分的成绩
let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
let scores2 = scores.filter(n => n>= 60)
console.log(scores2) // [95,91,82,72,85,67,66, 91]
reduce n变1
如图,计算数组中所有元素之和,除了用for循环之外,还可以只使用reduce
注:reduce中 0 是一开始的参数
reduce是数组变换中功能最为强大的,它还可以实现map,filter的功能:
- 用reduce将数组中的各元素平方
- 用reduce挑选出数组中的偶数
注:上方的if…else语句可以简化为下方的问号冒号表达式
例:
- 计算所有奇数的和
let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
let sum = scores.reduce((sum, n)=>{
return n%2===0?sum:sum+n
},0)
console.log(sum) // 奇数之和:598
- 数组去重
let arr = [11, 11, 2, 3, 3]
let newArr = arr.reduce((prev, cur, index, arr) => {
prev.indexOf(cur) === -1 && prev.push(cur);
return prev;
}, []);
console.log(newArr) //[11, 2, 3]
- 扁平一个二维数组
let arr = [
[1, 2, 8],
[3, 4, 9],
[5, 6, 10]
];
let res = arr.reduce((x, y) => x.concat(y), []);
console.log(res) //[1, 2, 8, 3, 4, 9, 5, 6, 10]
- 统计元素出现次数
let arr = [1,2,12,3,1,1,4,12,3,15];
let countedNumbers = arr.reduce(function (allNumbers, number) {
if (number in allNumbers) {
allNumbers[number]++;
}
else {
allNumbers[number] = 1;
}
return allNumbers;
}, {});
console.log(countedNumbers) //{1: 3, 2: 1, 3: 2, 4: 1, 12: 2, 15: 1}
一道面试题
let arr = [
{'名称': '动物', id: 1, parent: null},
{'名称': '猫', id: 2, parent: 1},
{'名称': '狗', id: 3, parent: 1}
]
数组变成对象
{
id:1,名称:'动物',children: [
{id:2,名称:'狗',children:null},
{id:2,名称:'猫',children:null},
]
}
解答:
let arr = [
{'名称': '动物', id: 1, parent: null},
{'名称': '猫', id: 2, parent: 1},
{'名称': '狗', id: 3, parent: 1}
]
let arr1 = arr.reduce(function (result, item) {
if (item.parent === null) {
result.id = item.id
result['名称'] = item['名称']
} else {
delete item.parent
item.children = null
result.children.push(item)
}
return result
}, {id: null, children: []})
console.dir(arr1)