JS数组 笔记

370 阅读7分钟

前言:JS其实没有真正的数组,它只是用对象模拟数组

JS的数组不是典型数组

典型的数组

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

JS的数组

  • 元素的数据类型可以不同
  • 内存不一定是连续的(因为对象是随机存储的)
  • 不是通过数字下标,而是通过字符串下标获取元素(写的数字会自动转变成字符串) image.png
    • 如:
    • 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('') image.png
  • Array.from('123')
    • 把不是数组的东西尝试变成数组
    • 条件1:这个对象有0,1,2,3这样的下标
    • 条件2:有一个length属性
    • image.png
    • 注:length的长度小于应有长度,会被自动截取
    • image.png
    • Array.from 可以把伪数组转换成数组

伪数组

伪数组的原型链中并没有数组的原型,所以它没有常见的数组操作方法
如图,我们定义一个数组
image.png

它的原型链中有数组的原型 [[Prototype]]:Array(0)也就是__proto__:Array(0)它的数组的独有的,也就数组的精髓,而在它之中才包含了普通公共的原型__proto__:Object
而伪数组并没有这样的原型

image.png

一个典型的伪数组

let divList = document.querySelectorAll('div')
这段代码创建的数组就是一个伪数组

image.png 可以看到它没有数组原型[[Prototype]]:Array(0),只有[[Prototype]]:NodeList
那怎么把它转化成数组?
很简单,使用Array.from即可
let divArray = Array.from(divList)

image.png

合并两个数组,得到新数组

  • arr1.concat(arr2)
    • 不会改变原来的两个数组
  • image.png

截取一个数组的一部分

  • arr.slice(1) 从数组下标为1的元素开始截取,原数组不变
  • arr.slice(1,4) 从数组下标为1的元素开始截取到下标为4-1的元素
  • image.png
  • arr1.slice(0) 全部截取
    • 其意义就是复制一个数组(因为JS不提供数组复制的方法)

注意:JS只提供浅拷贝

JS数组的增删改查

删元素

如果我们用删对象的方法删除
image.png
得到的数组会出现'empty',并且数组本身的长度不会改变
像这样数组中有'empty'的数组,称为稀疏数组,稀疏数组没有任何好处,只有BUG
所以不推荐这样的删法

如果直接改length呢?
image.png
可以实现删除,但是这样的方法也不是常规的删除方式,可能会出现BUG,所以也不推荐

正确的删除方法如下:

删除头部元素

  • arr.shift()
    • arr被修改,并返回被删元素,同时长度也会改变

删除尾部元素

  • arr.pop()
    • arr被修改,并返回被删元素,同时长度也会改变

删除中间元素

  • arr.splice(index,1) 删除下标为index的这一个元素
  • arr.splice(index,3) 删除下标为index开始的三个元素
    • image.png
  • arr.splice(index,1,'x') 删除下标为index的元素,并在原本的位置上添加x(可添加多个)
    • image.png

查看元素

同样地,不推荐使用查看对象的方式查看数组
我们可以用如下两个方法

查看数组的属性名和值

  1. for 循环打印
for(let i = 0; i < arr.length; i++){
   console.log(`${i}:${arr[i]}`)
}
  1. 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)})

得到如下结果:

image.png

分别打印出了数组的值,下标,和整个数组(通常省略不用)
所以foreach的本质就是用for循环遍历array的每一项,并对每一项都调用fn(array[i], i, array)

for循环和forEach有什么不同?
for是块,forEach是函数,
两者基本相同,一种情况不同:
for里可以有break和continue
如图,我们可以用for加break只遍历到数组的第四个元素

image.png

查看单个属性

  • arr[index] 查看下标为index的元素 注意索引越界
  • arr[arr.length]
  • arr[-1]
    • 两者都会返回 undefined
    • 因为没有index为-1和arr.length的值

索引越界例子:

image.png

出现报错,意为你读取了undefined的toString属性,它不能被读取
原因便是i<=arr.length处,这里不应该有等于号,因为没有下标会等于字符串长度

  • arr.indexOf(item) 查找某个元素是否在数组里,存在返回索引,否则返回-1
  • arr.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() 会修改元素组 如何反转一个字符串?
    将字符串转换为数组,反转之后再转回字符串

image.png

自定义顺序

(默认是从小到大排序)

我们有一个函数 arr.sort(function(a,b){return 1/0/-1})
如果给sort返回1(正数):左边大于右边
返回0:一样大
返回-1(负数):右边大于左边

知道这个原理就可以构造出一个排序:

image.png

然而我们知道sort是通过返回值的正负来判断顺序,所以上述的sort就可以简化为
arr.sort((a,b)=>a-b)

用这个方法来给成绩排序:

image.png

数组变换

注:数组变换都不会改变原数组

image.png

map n变n

image.png

例:把数字变成星期

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变少

image.png

例:找出所有大于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

image.png

注:reduce中 0 是一开始的参数

reduce是数组变换中功能最为强大的,它还可以实现map,filter的功能:

  1. 用reduce将数组中的各元素平方

image.png

  1. 用reduce挑选出数组中的偶数

image.png

注:上方的if…else语句可以简化为下方的问号冒号表达式

例:

  1. 计算所有奇数的和
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 
  1. 数组去重
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]
  1. 扁平一个二维数组
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]
  1. 统计元素出现次数
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)