JS高程之数组篇

昨天晚上重新把高程数组这块的知识重新看了一遍。本着看了一边肯定忘的原则,趁热总结一下这块的内容。

基础

新建数组

数组字面量表示法(常用)

1
2
3
4
let arrNull = [] // 建立个空数组,比较常用
let arrItem = ['red', 'black']
console.log('arrNull', arrNull, arrNull.length) // arrNull [] 0
console.log('arrItem', arrItem, arrItem.length) // arrItem  ["red", "black"] 2

注意: 不要写这种:let a = [1, 2, ]这种代码。1. 会导致后人看你这代码”这傻*,写的这是什么垃圾东西”。2. 它可能会创建包含二项或三项的的数组(浏览器实现不同)。在写代码时一目了然,否则出了错误找了半天结果是因为多写了个 , 会让人崩溃的。

构造函数方法

1
2
3
4
5
6
7
8
let a = new Array() //新建空数组
let b = new Array(5) // 新建一个长度为 5 的数组
let c = new Array('red') // 新建一个数组,包含 red 的数组,长度是 1
let d = new Array('red', 'black') // 新建一个数组,包含'red', 'black'的数组,长度是 2
console.log('a', a, a.length)
console.log('b', b, b.length, b.toString()) // 里面的每一项都是空的
console.log('c', c, c.length)
console.log('d', d, d.length)

提示: new 操作符可以省略,数组的每一项可以是各种数据类型。

调用

1
2
3
4
5
6
7
let arrItem = ['red', 'black']
let arrNum = [1, 2]
arrItem[0] // 获取,数组下标从 0 开始,所以应该是 'red'
arrItem[2] = 'blue' // 赋值 给数组长度+1 ,数组变为['red', 'black', 'blue']
arrItem.length = 4
arrItem[3] // 会输出 undefined
arrNum[arrNum.length] = 3 // 与数组的push方法效果一样

注意: 数组的length属性是可以赋值的,超出原数组的长度,再获取的话会是undefined。数组最多可包含42 9497 6295个项,创建一个初始大小相近的数组会导致运行时间过长报错 “Invalid array length”。

使用

检测数组

1
2
3
4
5
let a = [1, 2, 3]
console.log(a instanceof Array) // true
console.log(Array.isArray(a)) // true
console.log(a.constructor === Array) // true
console,log(Object.prototype.toString.call(a) === '[object Array]') // true

四种方法的优缺点:超判断js数据类型的四种方法,以及各自的优缺点(链接)

点我看大图

数组转换字符串

调用数组的 toString() 和 valueOf() 方法会返回相同的值,即:由数组中的每个值得字符串形式拼接而成的一个以逗号分隔的字符串。

toString() 和 valueOf()

1
2
3
4
let a = ['red', 'black']
console.log(a.toString()) // red,black
console.log(a.valueOf(), a.valueOf() instanceof Array) // ['red', 'black'] true
alert(`${a.valueOf()} ${a.valueOf() instanceof Array}`) // red,black true

注意:在日常使用 a.valueOf() 是返回的他本身 —— 数组,在很多教程里都写的是返回的是与 toString() 相同是个字符串,是因为它隐式的调用了 toString() 方法所以才会是字符串,例如最后一行的alert输出。${a.valueOf()}实际上转换成了 ${a.valueOf().toString()}

.join()

.join()方法只接受一个参数,即:用作分隔符的字符串,然后返回包含所有数组项的字符串。

1
2
3
4
let a = [1, 2, 3]
console.log(a.join(undefined)) // 传入undefined ,会默认使用 , 进行分割
console.log(a.join()) // 不传参数默认也是 , 进行分割
console.log(a.join(','))

补充

如果数组中的某一项的值是 null 或者是 undefined ,那么该值在 .join() .toLocaleString() .toString() .valueOf() 方法返回的结果中以空字符串表示。且上述方法均不会改变原数组。

模拟栈与队列

栈(Last-In-First-Out)

栈是一种后进先出的数据结构

.push()

.push()方法可以接受任意数量的参数,把他们逐个添加到数组末尾,并返回修改后的数组的长度

.pop()

.pop() 则从数组末尾移除最后一项,减少数组的 length值,然后返回被移除的项

实现
1
2
3
4
5
let a = [1, 2, 3]
let newPush = a.push(4) // 入栈 返回新数组的长度
console.log(a, newPush) // 查看新数组 [1, 2, 3, 4] 4
let newPop = a.pop() //最后一个出栈 返回被出栈的项
console.log(a, newPop) // [1, 2, 3] 4

队列(First-In-First-Out)

队列是一种先进先出的数据结构。

.shift()

.shift() 能够删除数组的第一项并返回该项,同时会把数组长度-1
.unshift() 能在数组前端添加任意项并返回新数组的长度。

实现
1
2
3
4
5
let a = [1, 2, 3]
let newPush = a.push(4)
console.log(a, newPush)
let newShift = a.shift() // 第一个出栈 ,返回出栈项
console.log(a, newShift) // [2, 3, 4] 1

重排序方法

.reverse()

1
2
3
let a = [1, 2, 3, 4, 5]
a.reverse()
console.log(a) // [5, 4, 3, 2, 1] 会改变原数组

不够灵活,只能反转数组。

.sort()

.sort()不传参数时,默认升序排列。为了实现排序,.sort()方法会调用数组每项的 toString() 方法,然后比较转换之后的字符串,以确定如何排序。

1
2
3
let a = [1, 2, 3, 12, 22, 4]
a.sort()
console.log(a) //[1, 12, 2, 22, 3, 4]

注意: 因为会调用数组每项的toSrting方法,所以在进行字符串比较时,’12’ 会排到 ‘2’ 的前面(1 < 2)。为了避免这种情况发生我们可以给 sort() 方法传一个方法。比较函数接受两个参数,如果第一个参数应该位于第二个参数之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应为第二个之后则返回一个正数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function compare (value1, value2) {
if (value1 < value2) {
return -1
}
else if (value1 > value2){
return 1
}
else {
return 0
}
}
let a = [1, 2, 3, 12, 22, 4]
a.sort(compare)
console.log(a)

对于数值类型或者 valueOf() 方法会返回数值类型的对象类型,可以使用简单的比较函数,这个函数只需要用第二个值减去第一个值即可。

1
2
3
4
5
let a = [1, 2, 3, 22, 12, 4]
a.sort((a,b) => {
return a - b
})
console.log(a) // 排序后的数组

操作方法

.concat()

.concat() 方法可以基于当前数组所有相创建一个新数组。具体来说,这个方法会先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给concat()方法传递参数的情况下,他只复制当前数组并返回副本。

1
2
3
4
5
6
let a = [1, 2, 3]
let b = [4, 5, 6]
let c = 'xnnnnn'
console.log(a.concat(b)) // [1, 2, 3, 4, 5, 6]
console.log(a.concat(c)) // [1, 2, 3, "xnnnnn"]
console.log(a) // [1, 2, 3]

.slice()

.slice() 能够基于当前数组的一或多个项创建一个新数组。可以接受一个或者两个参数,即要返回项的起始位置和结束位置(位置从一开始)。一个参数时,它返回从该参数指定位置开始到结尾。如果有两个参数的话就是从第一个参数开始第二个参数位置结束。

1
2
3
4
let a = [1, 2 ,3, 4 ,5]
a.slice(2,5)
console.log(a.slice(2,4)) // [3, 4]
console.log(a.slice(2)) // [3, 4, 5]

.splice()

.splice() 的主要用途是想数组的中部插入项,会返回一个数组,该数组包含原始数组中删除的项(没有删除则返回一个空数组),原数组会改变,使用这种方法的方式有三种:

删除

可以删除任意数量的项,只需指定两个参数:要删除的第一项的位置和要删除的项数。

1
2
3
let a = [1, 2, 3, 4, 5, 6, 7]
console.log(a.splice(0, 2)) //  [1, 2] 被删除项的数组
console.log(a) // [3, 4, 5, 6, 7] 修改后的数组
插入

可以向指定位置插入任意数量的项,需要提供三个参数: 起始位置、0(要删除的项数,因为是插入所以是0)和要插入的项。如果要插入多个项可以再传入第四、五个参数。

1
2
3
let a = [1, 2, 3, 4, 5, 6, 7]
console.log(a.splice(2, 0, 3.5)) // 因为没有删除所以会返回一个空数组
console.log(a) // [1, 2, 3.5, 3, 4, 5, 6, 7]
替换

可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定三个参数:起始位置、要删除的项数、要插入项

1
2
3
let a = [1, 2, 3, 4, 5, 6, 7]
console.log(a.splice(2, 2, 3.3, 4.4)) // 从第二位开始删除了两项 所以输出: [3, 4]
console.log(a) // [1, 2, 3.3, 4.4, 5, 6, 7]

迭代方法

every()

对数组中的每项运行给定函数,如果该函数每项都返回true,则这个函数返回 true。

some()

对数组中的每一项运行给定的函数,如果该函数对任一项都返回true,则返回true。

forEach()

对数组中的每一项运行给定的函数。该方法没有返回值

filter()

对数组中的每一项运行给定的函数,返回该函数会返回true的项组成的数组。

map()

对数组中的每一项运行给定的函数,返回每次函数调用的结果组成的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let number = [1, 2, 3, 4, 5, 6, 7]
let every = number.every((item, index, array) => {
return (item > 2)
})
console.log(every) // false 因为有比2小的项所以返回false
let some = number.some((item, index, array) => {
return (item > 2)
})
console.log(some) // true 因为有比2大的项
let filter = number.filter((item, index, array) => {
return (item > 4) // [5, 6, 7]
})
console.log(filter)
let mapArr = number.map((item, index, array) => {
return item * 2
})
console.log(mapArr) // [2, 4, 6, 8, 10, 12, 14]
let forEachArr = number.forEach((item, index, array) => {
if (item > 2) {
console.log(`大于2的有:${item}`)
} else {
console.log(`小于2的有:${item}`)
}
})

缩小方法(返回一个值)

reduce() 和 reduceRight()

这两个方法都会迭代数组的所有项,然后构建一个最终返回的值,其中 reduce() 方法充数租的第一项开始,逐个便利到最后。而reduceRight() 则从数组的最后一项开始,向前遍历到第一项。
这两个方法也都能接受两个参数:第一项是在每一项上调用的函数(非必须),和作为缩小基础的初始值。 其中函数可以接受四个形参 前一个值、当前值、项的索引、数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。所以第一次迭代发生在数组的第二项,因为第一项是函数的第一个参数。我们利用 reduce() 函数可以求和.

1
2
3
4
let number = [1, 2, 3, 4]
number.reduce((beforeElement, afterElement, key, number) => {
return beforeElement + afterElement
})

ES6增加

拓展运算符

拓展运算符(spread)是三个点。它好比rest参数的逆运算将一个数组转为用逗号分个的参数序列。主要用于函数调用。如果将扩展运算符用于数组赋值,智能放在参数的最后一位,否则会报错。

1
2
3
4
5
6
7
8
9
function add(a, b) {
return a + b
}
let numbers = [1, 2]
add(...numbers) // 3
function arr(...b){
return b
}
arr(1, 2, 3, 45, 6, 7, 8, 9, 0, 1)

上述代码函数的调用,使用了扩展运算符。该运算符将一个数组变为了参数序列。也可以将传入不确定个数的参数写成 function arr 这种形式。
只有函数调用时,拓展运算符才可以放到圆括号中。

数组合并

1
2
3
4
5
let a = [1, 2, 3]
let b = [4, 5, 6]
a.push(...b) // 两个数组平铺合并
[...a, ...b]
a.push(b)// 这种写法并不是平铺,而是末尾加了一项,这项是个数组

数组复制

值复制出一个新数组,不会影响到原有数组。

1
2
3
4
5
6
let a = [1, 2, 3, 4]
let b = a.slice()
let c = a.concat()
let d = Array.from(a)
let e = a.map(x => x)
let f = [...a]

查找元素(位置方法)

find()

用于找出第一个符合条件的数组成员,他的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回为true的成员。然后返回该成员。如果没有符合条件的成员,则返回undefined。

findIndex()

与find()用法类似,只不过他返回的是成员的位置,如果都不符合条件则返回 -1。

补充

find() 和 findIndex() ,都可以接受第二个参数用来绑定回调函数的 this 对象。另外,这两个方法都可以发现NaN,弥补了indexOf方法的不足。

indexOf()

indexOf 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1 。可以接收两个参数一个是要查找的项,另一个查找的是起始位置(负数是从后面开始)

lastIndexOf

这可以接受两个参数一个是要寻找的元素,第二个是从什么位置开始查找。indexOf是从前开始找。lastIndexOf是从数组末尾向前找,在他们内部都是采用的 “===” 全等操作符。他们俩返回的是元素在数组中的位置。(IE9+)

includes()

includes() 方法返回布尔值,表示某个数组是否包含给定的值。与字符串的includes方法类似。能传进去两个参数,一个是要查找的项,另一个是查找的起始位置(负数是从后面开始)

1
2
3
let a = [1, 2, 3, 4, 5, 6, 7]
a.indexOf(4) // 3
a.lastindexOf(4) // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
let a = [1, 2, 3, 4, 5]
a.find((value, index, Array) => {
if (value > 3) {
return value // 3 因为4是大于三的第一个元素。
}
})
a.findIndex((value, index, Array) => {
if (value > 3) {
return index // 3 因为4是大于三的第一个元素。
}
})
a.indexOf(4, 2) // 3
a.includes(4, 2) //从第三位开始查找(3 的位置),所以包含,所以返回true。

填充数组

fill 方法使用给定值,填充一个数组。它可以接收三个参数,第一个是填充的项,第二项是起始位置,第三项是结束位置。

1
2
let a = Array(3).fill(7) // [7, 7, 7]
[1, 2, 3, 4, 5].fill(7, 2, 5) // [1, 2, 7, 7, 7]

平铺数组

flat()

用于将嵌套的数组“拉平”,变成一维数组,并返回一个新数组。可接受一个参数表示要拉平的数组深度。如果参数是 Infinity 表示全部拉伸。

flatMap()

对原数组的每个成员执行一个函数(相当于map),然后对返回值组成的数组执行 flat() 方法。该方法返回一个新数组,不改变原数组。

1
2
[1, [2, [3]]].flat(Infinity) // [1, 2, 3] 
[2, 3, 4].flatMap((x) => [x, x +1]) // [2, 3, 3, 4, 4, 5]

entries()、keys()、values()

这三个新方法用于遍历数组。他们都返回一个遍历器对象。可用于for…of 循环进行遍历.区别keys() 是对键名的遍历values() 是对键值的遍历entries() 是对键值对的遍历,返回的是键值对的数组。

1
2
3
4
5
6
7
8
9
10
let a = [1, 2, 3, 4, 5]
for(element of a.values()){
console.log(element)
}
for(element of a.keys()){
console.log(element)
}
for(element of a.entries()){
console.log(element)
}

拓展实现

数组模糊搜索

之前面试被问到了如何用数组做一个模糊搜索,当时没答上。今天突然想起来,然后就顺手补上吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let a = ["1dasdadasddax", "dfdfdfdfttttt", "xcvxvxvcxvxcv6", 6, null,undefined]
function search (value, Arr){
newArr = Arr.flat(Infinity)
let b = Array()
for(let i of newArr){
if (i === null || i === undefined){
if(value === i) {
b.push(value)
}
}
else if (i.toString().includes(value)) {
b.push(i)
}
}
return b.length > 0? b: '不存在'
}
search('6', a)

本来是想把高程关于数组这块总结一下的没想到写着写着东西越来越多,そうしましょう,以后我想到还会过来补充的~
2020.8.1日更新,复习ES6语法新增

Array.from

能把类数组对象(array like object)和带有可遍历对象的iterator的对象转换成数组。
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  //类数组对象
let dom = document.getElementByClassName('calssName') // 返回的NodeList集合
let domArray = Array.from(dom) // 转换成正真的数组
// iterator的对象,必须带有length属性才能够转换,key和下标对应的上
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
Array.from(arrayLike); // ['a', 'b', 'c']

// 反例
let a ={
v:1,
r:2,
i:3,
length:3
}
Array.from(a) // [undefined, undefined, undefined]
// 两个参数
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
  • 相同功能的有: 扩展运算符(…),他也能转换成数组但是她本质是调用iterator接口,对象没有此接口的话就无法转换。且不能转换类数组对象。
    利用Array.from 获取String的长度,可以避免 JavaScript 将大于\uFFFF的 Unicode 字符,算作两个字符的 bug。
    1
    2
    let a = 'xnnnnnnnnnnn'
    Array.from(a).length

感谢您的阅读。 🙏