学习JavaScript数据结构与算法(一)— 数组

475 阅读10分钟

前言

本人是一个刚入行的菜鸡前端程序员,写这个文章的目的只是为了记录自己学习的笔记与成果,如有不足请大家多多指点。

数组

数组是最简单的内存数据解构,数组存储一系列同一种数据类型的值。虽然在JavaScript里,也可以在数组中保存不同类型的值,但多数时候我们还是遵守最佳实践,要避免这么做。

创建和初始化数组

用 JavaScript 声明、创建和初始化数组很简单

let daysOfWeek = new Array()  // (1)
daysOfWeek = new Array(7) //(2)
daysOfWeek = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday') //(3)

使用new关键字,就能简单地声明并初始化一个数组(行(1))。用这种方式还可以创建一个指定长度的数组(行(2))。我们也可以直接将数组元素作为参数传递给它的构造器(行(3))。 用new创建数组并不是最好的方式。如果你想在JavaScript中创建一个数组,只用中括号([])的形式就行了,如下:

let daysOfWeek = []

也可以使用一些元素初始化数组 :

let daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

想知道数组中已经存了多少个元素,可以使用数组的length属性。

console.log(daysOfWeek.length) // 7

访问元素和迭代数组

要访问数组里特定位置的元素,可以用中括号传递数值位置,得到想知道的值或者赋新的值。假如我们想输出数组 daysOfWeek 里的所有元素,可以通过循环迭代数组、打印元素,如下:

for (let i = 0; i < daysOfWeek.length; i++) {
    console.log(daysOfWeek[i])
}

例子:求斐波那契数列的前20个数。已知斐波那契数列中的前两项是 1,从第三项开始,每一项都等于前两项之和。

const fibonacci = [];
fibonacci[1] = 1;
fibonacci[2] = 1;

for (let i = 3; i < 20; i++) {
    fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2] 
}

for (let i = 1; i < fibonacci.length; i++) {
    console.log(fibonacci[i]) 
}

如果想知道斐波那契数列其他位置上的值是多少,把循环条件中的终止变量从20改成你希望的值就行了

添加元素

在数组中添加和删除元素也很容易,但有时也会很棘手。假如我们有一个数组 numbers,初始化成了0到9

let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

在数组的末尾插入元素

如果想要给数组添加一个元素(比如10),只要把值赋给数组中最后一个空位上的元素即可

numbers[number.length] = 10
使用push方法

还有一个push方法,能把元素添加到数组的末尾。通过push方法我们能添加任意个元素。

numbers.push(11);
numbers.push(12,13)

如果输出numbers的话,就会看到从0到13的值

在数组开头插入元素

我们希望在数组中插入一个新元素(数-1),不像之前那样插入到最后,而是放到数组的开头。为了实现这个需求,首先要腾出数组里第一个元素的位置,把所有的元素向右移动一位。我们可以循环数组中的元素,从最后以为(长度值就是驻足的末尾位置)开始,将对应的前一个元素(i-1)的值赋给它(i),依次处理,最后把我们想要的值赋给第一个位置(索引0上)。我们可以将这段逻辑写成一个函数,甚至将该方法直接添加在 Array 的原型上,使所有数组的实例都可以访问到该方法。

Array.prototype.insertFirstPosition = function(value) {
    for (let i = this.length; i >= 0; i--) {
        this[i] = this[i - 1];
    }
    this[0] = value
}

numbers.insertFirstPosition(-1)
使用 unshift 方法

在JavaScript里,数组有一个方法叫 unshift,可以直接把数值插入数组的开头(此方法背后的逻辑和insertFirstPostion方法的行为是一样的)

numbers.unshift(-2);
numbers.unshift(-4, -3);

删除元素

从数组末尾删除元素

要删除数组里最靠后的元素,可以使用 pop 方法

numbers.pop()
从数组开头删除元素

如果要移除数组里的第一个元素,可以用下面的代码

for (let i = 0; i < numbers.length; i++) {
    numbers[i] = numbers.[i - 1]
}

但是,以上代码我们只是把数组第一位的值用第二位覆盖了,并没有实现真正意义上的删除(因为数组的长度还是和之前一样,并且多了一个未定义元素) 要真正从数组中移除这个元素,我们需要创建一个新的数组,将所有不是undefined的值从原来的数组复制到新的数组中,并且将这个新的数组赋值给我们的数组。创建一个 reIndex 方法

Array.prototype.redIndex = function(myArray) {
    const newArray = []
    for (let i = 0; i < myArray.length; i++) {
        if(myArray[i] !== undefined) {
            newArray.push(myArray[i])
        }
    }
    return newArray
}

手动移除第一个元素并重新排序

Array.prototype.removeFirstPosition = function () {
    for (let i = 0; i < this.length; i++) {
        this[i] = this[i + 1]
    }
    return this.reIndex(this)
}

numbers = numbers.removeFirstPosition()

注:上面的代码只是示范作用,用于理解原理,在实际使用中我们应该始终使用 shift 方法

使用shift 方法

要删除数组的第一个元素,可以用shift 方法实现 numbers.shift()

在任意位置添加或删除元素

我们可以使用 splice 方法,简单地通过指定位置/索引,就可以删除相应位置上指定数量的元素。

numbers.splice(5,3)

删除从数组索引5开始的3个元素。 现在,我们想把 2、3、4 插入数组里,放到之前删除元素的位置上,可以再次使用splice方法。

numbers.splice(5, 0, 2, 3, 4)

splice 方法接收的第一个参数,表示想要删除或插入的元素的索引值。第二个参数是删除元素的个数。第三个参数往后,就是要添加到数组里的值。

二维和多维数组

JavaScript只支持一维数组,并不支持矩阵。但是我们可以通过数组套数组,实现矩阵或任意多维数组。

let averageTemp = [] 
averageTemp[0] = [23, 45, 35, 56, 33, 34]
averageTemp[1] = [32, 35, 23, 34, 23, 12]
迭代二维数组的元素

如果想看这个矩阵的输出,可以创建一个通用函数,专门输出其中的值。

function printMatrix(myMatrix) {
    for (let i = 0; i < myMatrix.length; i++) {
        for (let j = 0; j < myMatrix[i].length; j++) {
            console.log(myMatrix[i][j])
        }
    }
}
多维数组

我们也可以用这种方式来处理多维数组。假设我们要创建一个 3 x 3 x 3 的矩阵,每一格里包含矩阵的i(行)、j(列)及z(深度)之和

const matrix3x3x3 = [] 
for (let i = 0; i < 3; i++) {
    matrix3x3x3[i] = [] // 我们需要初始化每个数组 
    for (let j = 0; j < 3; j++) {
        matrix3x3x3[i][j] = []
        for (let z = 0; z < 3; z++) {
            matrix3x3x3[i][j][z] = i + j + z
        }
    }
}

数组合并

有多个数组,需要合并起来成为一个数组。JavaScript给我嗯提供了解决方法,叫作 concat 方法。

const zero = 0;
const positiveNumbers = [1, 2, 3];
const negativeNumbers = [-3, -2, -1];
let numbers = negativeNumbers.concat(zero,positiveNumbers)

concat 方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序连接指定数组。zero将被合并到positiveNumvers中,然后positiveNumbers继续被合并。

迭代器函数

有时我们需要迭代数组中的元素,可以用循环语句来处理,例如for。 JavaScript 内置了许多数组迭代的方法。 假设数组中的值是从1到15;如果数组里的元素可以被2整除,函数就返回true,否则返回false。

function isEven(x) {
    //如果 x 是 2 的倍数,就返回true
    return x % 2 === 0 ? true : false
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
用every方法迭代

every 方法会迭代数组中的每个元素,直到返回 false。

numbers.every(isEven);

数组numbers的第一个元素是1,它不是2的倍数,因此 isEven 函数返回 false, 然后 every 执行结束。

用some方法迭代

some和every的行为相反,会迭代数组的每个元素,直到函数返回true

numbers.some(isEven);

numbers数组中的第一个偶数是2。isEven 返回 true —— 迭代结束

用forEach方法迭代

如果要迭代整个数组,可以用forEach方法。它和for循环的结果相同。

numbers.forEach( x => console.log( x % 2 === 0))
使用 map 和 filter 方法

这两个方法都会返回一个新的数组

const myMap = numbers.map(isEven)

myMap它保存了传入 map 方法的 isEven 函数的运行结果。 mymap 数组中的元素都是 true,false 组成。

const evenNumbers = number.filter(isEven)

filter 方法,它返回的新数组由函数返回 true 的元素组成。 evenNumbers 数组中的元素都是偶数。

使用reduce方法

reduce 方法接收一个有4个参数的函数: previousValue,currentValue,index和array。index和array是可选参数,用不到可以不用传。如果要对一个数组求和,这就很有用。

numbers.reduce( (previous,current) => previous + current )

ES6数组新功能

1.使用for...of 循环迭代
for (const n of numbers) {
    console.log( n % 2 === 0 ? 'even' : 'odd')
}
2.使用@@iterator对象

ES6还为Array类增加一个@@iterator属性,需要通过 Symbol.iterator 来访问。

let iterator = numbers[Symbol.iterator]();
console.log(iterator.next().value) // 1

然后不断调用迭代器的 next 方法,就能依次得到数组中的值。数组中的所有制都迭代完成之后,iterator.next().value 会返回 undefined

3.使用from方法

Array.from 方法根据已有的数组创建一个新数组。比如,要复制numbers数组

let numbers2 = Array.from(numbers)

还可以传入一个用来过滤值的函数

let evens = Array.from(numbers, x => (x % 2 === 0))
4.使用Array.of方法

Array.of 方法根据传入的参数创建一个新数组。

let numbers3 = Array.of(1);
let numbers4 = Array.of(1, 2, 3, 4, 5, 6);

它和下面这段代码的效果一样

let numbers3 = [1]
let numbers4 = [1, 2, 3, 4, 5, 6]
5.使用fill方法

fill 方法用静态值填充数组

let numbersCopy = Array.of(1, 2, 3, 4, 5, 6)

numbersCopy数组的length是6

numbersCopy.fill(0)

numbersCopy数组所有位置上的值都会变成0。我们还可以指定开始填充的索引。

numbersCopy.fill(2, 1)

数组中从1开始的所有位置上的值都是2。 同样我们还可以指定结束填充的索引。

numbersCopy.fill(1, 3, 5)

把1填充到3到5的位置(不包括3和5)

6.使用copyWithin方法

copyWithin方法复制数组中的一系列元素到同一数组指定的起始位置。

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

假如我们想把4、5两个值(在位置3、4上)复制到位置1和2,可以这样做:

copyArray.copyWithin(1, 3, 5)

会把位置3开始到位置5结束(包括3不包括5)的元素复制到位置1,结果得到数组 [1, 4, 5, 4, 5, 6]

排序元素

1.reverse

我们想反序输出数组 nummbers。要实现这样的功能,可以用reverse方法,然后数组内元素就会反序。

2.sort

sort 方法在对数组做排序时,把元素默认成字符串进行相互比较。 我们可以传入自己写的比较函数。

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

搜索

搜索有两个方法:indexOf方法返回与参数匹配的第一个元素的索引;lastIndexOf返回与参数匹配的最后一个元素的索引。

console.log(numbers.indexOf(10)) 
console.log(numbers.lastIndexOf(10)) 

ES6 - find 和 findIndex 方法

find 和 findeIndex 方法接收一个回调函数,搜索一个满足回调函数条件的值。

let numbers = [1,2,3,4,5,6,7,8,9,10]
function multipleOf7(element, index, array) {
    return (element % 7 == 0)
}
console.log(numbers.find(multipleOf7)) 
console.log(numbers.findIndex(multipleOf7))

find 方法返回第一个满足条件的值, findIndex 方法则返回这个值在数组里的索引。如果没有满足条件的值, find 会返回 undefined ,而 findeIndex 返回 -1。

includes方法

如果数组里存在某个元素,includes 方法会返回true,否则返回false。

numbers.includes(14)

输出数组为字符串 toString 和 join

如果想把数组里所有的元素输出为一个字符串,可以用toString方法。

let arr = [1, 2, 3, 4, 5]
console.log(arr.toString()) // 1、2、3、4、5

如果想用一个不同的分隔符把元素隔开,可以用 join 方法。

let arr = [1, 2, 3, 4, 5]
console.log(arr.join('-')) // 1-2-3-4-5