关于数组这个数据结构
1、访问元素
要访问数组里特定位置的元素,可以用中括号传递数值位置,得到想知道的值或者赋新的值。
假如我们想输出数组arr里的所有元素,可以通过循环遍历数组,打印元素,如下所示:
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
例子:求斐波那契数列的前 20 个数字。已知斐波那契数列中第一个数字是 1, 第二个是 2,从第三项开始,每一项都等于前两项之和:
斐波那契数列为:1 1 2 3 5 8 13 21...(这里题设第一个数为 1,所以代码设计去掉了数列的第一个 1)
var fibonacci = []; //声明并创建了一个数组
fibonacci[1] = 1; //按题意赋值
fibonacci[2] = 2; //按题意赋值
for (var i = 3; i < 20; i++) {
fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2]; //循环赋值
}
for (var i = 1; i < fibonacci.length; i++) { //循环遍历
console.log(fibonacci[i]); //打印每次遍历的元素
}
console.log(fibonacci);//输出数组
2、数组中添加元素
注意:在JavaScript中,数组是一个可以修改的对象。如果添加元素,它就会动态增长。在 C 和 Java 等其他语言里,我们要先决定数组的大小,想添加元素就要创建 一个全新的数组,不能简单地往其中添加所需的元素。
初始化一个数组:
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
(1)在数组末位插入元素——使用 push 方法
思想:给数组添加一个元素(比如 10),只要把值赋给数组中最后一个空位上的元素即可
arr[arr.length] = 10;
使用push方法
arr.push(11);
(2)在数组首位插入元素——使用 unshift 方法
思想:
- 要腾出数组里第一个元素的位置,把所有的元素向右移动一位。
- 循环数组中的元素,从最后一位(
arr[arr.length])开始,将其对应的前一个元素的值赋给它,依次处理 - 最后把想要的值赋给第一个位置(
arr[0])上
for (var i = arr.length; i >= 0; i--) {
arr[i] = arr[i - 1];
}
arr[0] = -1; // -1 为自定义值
使用unshift方法
arr.unshift(-1);
3、数组中删除元素
(1)删除数组末位元素——使用 pop 方法
arr.pop(11)
(2)删除数组首位元素——使用 shift 方法
思想:把数组里所有的元素都左移了一位
for (var i = 0; i < numbers.length; i++) {
numbers[i] = numbers[i + 1];
}
注意:这样的话,数组的长度仍然是原长度,即数组中还有额外的一个元素(未定义,值是undefined)。只是把数组第一位的值用第二位覆盖了,并没有删除元素。
要确实删除数组的第一个元素,可以用shift方法实现:
arr.shift();
4、在任意位置添加或删除元素——使用 splice 方法
使用splice方法,简单地通过指定位置/索引,就可以删除相应位置和数量的元素:
arr.splice(5, 3); //删除了从数组索引 5 开始的 3 个元素
注意:对于JavaScript数组和对象,我们还可以用delete操作符删除数组中的元素,例如delete numbers[0] 。然而,数组位置 0 的值会变成undefined,也就是说,以上操作等同于numbers[0] = undefined 。因此,我们应该始终使用splice、pop或shift方法来删除数组元素。
把数字 2、3、4 插入数组里:
var arr = [0, 1, 2, 3, 4, 5, 6];
arr.splice(5, 0, 2, 3, 4);
console.log(arr); //[0, 1, 2, 3, 4, 2, 3, 4, 5, 6]
var arr = [0, 1, 2, 3, 4, 5, 6];
arr.splice(5, 1, 2, 3, 4);
console.log(arr); //[0, 1, 2, 3, 4, 2,3, 4, 6] 注意是删除第5个元素后的元素
- 第一个参数:要在其后删除或插入元素 的元素的索引值(在该元素后做操作)
- 第二个参数:删除元素的个数(上面例子里,我们的目的不是删除元素,所以传入 0)。
- 第三个参数往后:要添加到数组里的值(元素 2、3、4)
5、二维和多维数组
JavaScript只支持一维数组,并不支持矩阵。但是,我们可以用数组套数 组,实现矩阵或任一多维数组。
//day 1
averageTemp[0] = [];
averageTemp[0][0] = 72;
averageTemp[0][1] = 75;
averageTemp[0][2] = 79;
averageTemp[0][3] = 79;
averageTemp[0][4] = 81;
averageTemp[0][5] = 81;
//day 2
averageTemp[1] = [];
averageTemp[1][0] = 81;
averageTemp[1][1] = 79;
averageTemp[1][2] = 75;
averageTemp[1][3] = 75;
averageTemp[1][4] = 73;
averageTemp[1][5] = 72;
(1)迭代二维数组的元素
function printMatrix(myMatrix) {
for (var i = 0; i < myMatrix.length; i++) {
for (var j = 0; j < myMatrix[i].length; j++) {
console.log(myMatrix[i][j]);
}
}
}
需要遍历所有的行和列。因此,我们需要使用一个嵌套的for循环来处理,其中变量i为行,变量j为列。
(2)多维数组
for (var i = 0; i < matrix3x3x3.length; i++) {
for (var j = 0; j < matrix3x3x3[i].length; j++) {
for (var z = 0; z < matrix3x3x3[i][j].length; z++) {
console.log(matrix3x3x3[i][j][z]);
}
}
}
6、数组方法一览
一般方法:
| 方法名 | 描述 |
|---|---|
| concat | 连接 2 个或更多数组,并返回结果 |
| every | 对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true(迭代到结束或出现 false 为止) |
| filter | 对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组 |
| forEach | 对数组中的每一项运行给定函数。这个方法没有返回值 |
| join | 将所有的数组元素连接成一个字符串 |
| indexOf | 返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1 |
| lastIndexOf | 返回在数组中搜索到的与给定参数相等的元素的索引里最大的值 |
| map | 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组 |
| reverse | 颠倒数组中元素的顺序 |
| slice | 传入索引值,将数组里对应索引范围内的元素作为新数组返回 |
| some | 对数组中的每一项运行给定函数,如果任一项返回 true,则返回 true(迭代到结束或出现 true 为止) |
| sort | 按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数 |
| toString | 将数组作为字符串返回 |
| valueOf | 和 toString 类似,将数组作为字符串返回 |
一个迭代器函数
先看个例子
假如有 一个数组,它值是从 1 到 15,如果数组里的元素可以被 2 整除(偶数),函数就返回 true,否则返 回 false:
const isEven = function (x) {
// 如果x是2的倍数,就返回true
console.log(x);
return x % 2 == 0 ? true : false; //这里直接也可用 return (x % 2 == 0)
};
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
ES6 新增方法
| @@iterator | 返回一个包含数组键值对的迭代器对象,可以通过同步调用得到数组元素的键值对 |
|---|---|
| copyWithin | 复制数组中一系列元素到同一数组指定的起始位置 |
| entries | 返回包含数组所有键值对的@@iterator |
| includes | 如果数组中存在某个元素则返回 true,否则返回 false。ES7 新增 |
| find | 根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素 |
| findIndex | 根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素在数组中的索引 |
| fill | 用静态值填充数组 |
| from | 根据已有数组创建一个新数组 |
| keys | 返回包含数组所有索引的@@iterator |
| of | 根据传入的参数创建一个新数组 |
| values | 返回包含数组中所有值的@@iterator |
| some | |
| sort | |
| toString | |
| valueOf |
几个 ES6 方法的使用
from
Array.from 方法根据已有的数组创建一个新数组。比如,要复制 numbers 数组,可以这样做:
let numbers2 = Array.from(numbers);
还可以传入一个用来过滤值的函数,例子如下:
let evens = Array.from(numbers, (x) => x % 2 == 0);
上面的代码会创建一个 evens 数组,其中只包含 numbers 数组中的偶数。
fill
fill 方法用静态值填充数组。以下面的代码为例:
let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);
numbersCopy 数组的 length 是 6,也就是有 6 个位置。再看下面的代码:
numbersCopy.fill(0);
numbersCopy 数组所有位置的值都会变成 0([0, 0, 0, 0, 0, 0])。
我们还可以指定开始填充的索引,如下:
numbersCopy.fill(2, 1);
上面的例子里,数组中从 1 开始的所有位置,值都是 2([0, 2, 2, 2, 2, 2])。
同样,也可以指定结束填充的索引:
numbersCopy.fill(1, 3, 5);
上面的例子里,我们会把 1 填充到数组索引 3 到 5 的位置(不包括 5),得到的数组为[0, 2, 2, 1, 1, 2]。
创建数组并初始化值的时候,fill 方法非常好用,就像下面这样:
let ones = Array(6).fill(1);
上面的代码创建了一个长度为 6,所有的值都是 1 的数组([1, 1, 1, 1, 1, 1])。
关于数组的元素简单排序
arr.reverse():反序输出数组
关于 sort 方法,需要注意
sort 方法在对数组做排序时,把元素默认成字符串进行相互比较。
let arr = [1, 2, 3, 4, 5, 10, 11];
console.log(arr.sort()); //[1, 10, 11, 2, 3, 4, 5]
解决办法:传入自己写的比较函数
let arr = [1, 2, 3, 4, 5, 10, 11];
arr.sort(function (a, b) {
return a - b;
});
console.log(arr); //[1, 2, 3, 4, 5, 10, 11]
思想解释:sort 方法接受一个函数作为参数,然后 sort 会用它排序数组
以上方法相当于如下过程:
function compare(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
// a必须等于b
return 0;
}
arr.sort(compare);
自定义排序
可以对任何对象类型的数组排序,也可以创建比较函数来比较元素
例子:对象 Person 有名字和年龄属性,我们希望根据年龄排序,就可以这么写:
let friends = [
{ name: "John", age: 30 },
{ name: "Ana", age: 20 },
{ name: "Chris", age: 25 },
];
function comparePerson(a, b) {
if (a.age < b.age) {
return -1;
}
if (a.age > b.age) {
return 1;
}
return 0;
}
console.log(friends.sort(comparePerson));
字符串排序
思考:如下代码输出的是?
let names = ["Ana", "ana", "john", "John"];
console.log(names.sort());
//["Ana", "John", "ana", "john"]
JavaScript 在做字符比较的时候,是根据字符对应的 ASCII 值来比较的。例如,A、J、a、j 对应的 ASCII 值分别是 65、75、97、106。
数组扁平化
问题抛出:对于[1, [1,2], [1,2,3]这样多层嵌套的数组,我们如何将其扁平化为[1, 1, 2, 1, 2, 3]这样的一维数组呢(数组降维):
1、ES6 的 flat()
先来简单看一下关于它的使用:
const arr = [1, [1, 2], [1, 2, 3]];
arr.flat(Infinity); // [1, 1, 2, 1, 2, 3]
note: MDN:flat() 方法会按照一个可指定的深度(传入的参数)递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
Array.prototype.flat() 特性总结:
- 用于将嵌套的数组扁平化,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
- 不传参数时,默认扁平化一层,可以传入一个整数,表示想要扁平化的层数。
- 传入 <=0 的整数将返回原数组,不扁平化
- Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
- 如果原数组有空位,Array.prototype.flat() 会跳过空位。
方法虽好,但是使用这个方法前要先考虑一下兼容性问题哦!!!
我们来实现一个 flat吧!!!
note: MDN:reduce() 方法对数组中的每个元素执行一个由提供的 reducer 函数(升序执行),将其结果汇总为单个返回值
// 传入两个参数:数组 arr 扁平化层数 depth(默认是一层)
function flat(arr, depth = 1) {
return depth > 0 // 层数大于 0 才进行扁平化
? arr.reduce((acc, cur) => {
// acc:reducer 函数的返回值(累计器),cur:当前值(arr 中遍历到的当前元素值)
if (Array.isArray(cur)) {
// 当前值是 数组
return [...acc, ...flat(cur, depth - 1)]; // 递归执行 flat 函数,插入返回数组中 累计器的值后面
}
return [...acc, cur]; // 当前值不是数组,直接插入返回数组中 累计器后面
}, [])
: arr; // 扁平化层数小于等于 0,直接返回原数组 arr
}
2、序列化后正则
const arr = [1, [1, 2], [1, 2, 3]];
const str = `[${JSON.stringify(arr).replace(/(\[|\])/g, "")}]`;
JSON.parse(str); // [1, 1, 2, 1, 2, 3]
3、递归
对于树状结构的数据,最直接的处理方式就是递归
const arr = [1, [1, 2], [1, 2, 3]];
function flat(arr) {
let result = [];
for (const item of arr) {
item instanceof Array
? (result = result.concat(flat(item)))
: result.push(item);
}
return result;
}
flat(arr); // [1, 1, 2, 1, 2, 3]
4、reduce()递归
const arr = [1, [1, 2], [1, 2, 3]];
function flat(arr) {
return arr.reduce((acc, cur) => {
return acc.concat(cur instanceof Array ? flat(cur) : cur);
}, []);
}
// 或者
function flat(arr) {
return Array.isArray(arr)
? arr.reduce((acc, cur) => [...acc, ...flattenDeep(cur)], [])
: [arr];
}
flat(arr); // [1, 1, 2, 1, 2, 3]
5、迭代+展开运算符
// 每次 while 都会合并一层的元素,这里第一次合并结果为[1, 1, 2, 1, 2, 3, [4,4,4]]
// 然后 arr.some 判定数组中是否存在数组,因为存在[4,4,4],继续进入第二次循环进行合并
let arr = [1, [1, 2], [1, 2, 3, [4, 4, 4]]];
while (arr.some(Array.isArray)) {
arr = [].concat(...arr);
}
console.log(arr); // [1, 1, 2, 1, 2, 3, 4, 4, 4]
数组去重
首先全局定义一个数组,让我们开始由复杂到简单的方法探索一下吧!!!
var arr1 = [11, 11, 1, 1, 2, 2, "huo", "huo"];
1、双重 for 循环+新数组
思想:建立一个新数组,并放入原数组的第一个元素。将原数组元素依次与新数组元素比较,元素不同就放入新数组。最后返回新数组,达到去重效果。
function darray2(arr) {
let newArr = [arr[0]];
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < newArr.length; j++) {
var repeat = false; //这里一开始我用了 let 声明,会报错
//因为后面的 if 判断里 repeat 取不到 let 声明的块级作用域
if (arr[i] === newArr[j]) {
repeat = true;
break;
}
}
if (!repeat) {
newArr.push(arr[i]);
}
}
return newArr;
}
console.log(darray2(arr1)); // [11, 1, 2, "huo"]
优化一下:
for (let i = 0; i < arr.length; i++) {
let repeat = false; // 放到这里就行了,let的块级作用域包含后面的if
for (let j = 0; j < newArr.length; j++) {
if (arr[i] === newArr[j]) {
// var repeat = false;
repeat = true;
break;
}
}
if (!repeat) {
newArr.push(arr[i]);
}
}
2、双重 for 循环 + splice(第一种方法的简化)
思想:双层循环,外层循环元素,内层循环比较元素值。值相同则删除。
function darray1(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
console.log(darray1(arr1)); // [11, 1, 2, "huo"]
3、sort 排序+相邻元素比较
思想:使用 sort()方法将原数组排序。创建新数组,第一个元素为原数组第一个元素。原数组相邻元素进行比较,不同则放入新数组。最后返回新数组。
function darray3(arr) {
arr = arr.sort();
let newArr = [arr[0]];
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
newArr.push[arr[i]];
}
}
return newArr;
}
console.log(darray3(arr1)); // [11, 1, 2, "huo"]
4、indexOf 方法+遍历原数组元素
思想:创建新的空数组。遍历原数组,使用 indexOf 方法(查找某个元素的位置,如果不存在就返回-1)依次遍历原数组元素,判断新数组中是否含有该元素,有则放入新数组。最后返回新数组。
function darray4(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i]);
}
}
return newArr;
}
console.log(darray4(arr1)); // [11, 1, 2, "huo"]
5、includes 方法+遍历原数组元素
思想:创建新的空数组。遍历原数组,使用 includes 方法(判断是否包含某一元素,包含返回 true,不包含返回 false)依次遍历原数组元素,判断新数组中是否含有该元素,有则放入新数组。最后返回新数组。
function darray5(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (!newArr.includes(arr[i])) {
newArr.push(arr[i]);
}
}
return newArr;
}
console.log(darray5(arr1)); // [11, 1, 2, "huo"]
6、对象特性(不建议使用)
思想:利用对象的属性不能相同的特点进行去重
function darray6(arr) {
let newArr = [];
let obj = {};
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
newArr.push(arr[i]);
obj[arr[i]] = 1;
} else {
obj[arr[i]]++;
}
}
return newArr;
}
console.log(darray6(arr1)); // [11, 1, 2, "huo"]
7、filter +includes
思想:使用过滤器,过滤重复的元素,返回新数组
function darray7(arr) {
let newArr = [];
newArr = arr.filter(function (item) {
return newArr.includes(item) ? "" : newArr.push(item);
});
return newArr;
}
console.log(darray7(arr1)); // [11, 1, 2, "huo"]
8、filter + hasOwnProperty
思想:利用 hasOwnProperty 判断是否存在对象属性
function darray8(arr) {
let obj = {};
return arr.filter(function (item, index, arr) {
return obj.hasOwnProperty(typeof item + item)
? false
: (obj[typeof item + item] = true);
});
}
console.log(darray8(arr1)); // [11, 1, 2, "huo"]
9、递归
function darray9(arr) {
let array = arr;
let len = array.length;
array.sort(function (a, b) {
//排序后更加方便去重
return a - b;
});
function loop(index) {
if (index >= 1) {
if (array[index] === array[index - 1]) {
array.splice(index, 1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len - 1);
return array;
}
console.log(darray9(arr1)); // [1, 2, 11, "huo"]
10、利用 Map 数据结构
function darray10(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if (map.has(arr[i])) {
// 如果有该 key 值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果没有该 key 值
array.push(arr[i]);
}
}
return array;
}
console.log(darray10(arr1)); // [11, 1, 2, "huo"]
11、利用 reduce+includes
function darray11(arr) {
return arr.reduce(
(prev, cur) => (prev.includes(cur) ? prev : [...prev, cur]),
[]
);
}
12、利用 Set 数据结构 + Array.from
function darray12(arr) {
//Set数据结构,它类似于数组,其成员的值都是唯一的
return Array.from(new Set(arr)); // 利用Array.from将Set结构转换成真正的数组
}
console.log(darray12(arr1)); //[11, 1, 2, "huo"]
13、利用 Set 数据结构 + 展开运算符(最简便的方法了)
let arr = [...new Set(arr1)];
console.log(arr); // [11, 1, 2, "huo"]