3. 数组
创建和初始化数组
let daysOfWeek = new Array(); // 使用new关键字,声明并初始化一个数组
daysOfWeek = new Array(7); // 创建一个制定长度的数组
daysOfWeek = new Array(
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
); // 将数组元素作为参数传递给数组的构造器
let daysOfWeek = []; // 直接用中括号([])创建数组
let daysOfWeek = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
console.log(daysOfWeek.length); // 使用length获取数组中元素的数量
访问元素和迭代数组
for (let i = 0; i < daysOfWeek.length; i++) {
console.log(daysOfWeek[i]);
}
求斐波那契数列的前 20 个数。已知斐波那契数列中的前两项是 1,从第三项开始,每一项都等于前两项之和。
const fibonacci = []; // {1}
fibonacci[1] = 1; // {2}
fibonacci[2] = 1; // {3}
for (let i = 3; i < 20; i++) {
fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2]; // {4}
}
for (let i = 1; i < fibonacci.length; i++) {
// {5}
console.log(fibonacci[i]); // {6}
}
数组的方法
- push:在数组的末尾添加元素
numbers.push(11); numbers.push(12, 13);
- unshift:在数组的开头添加元素
numbers.unshift(-2); numbers.unshift(-4, -3);
- pop:在数组的末尾删除元素
numbers.pop();
- shift:在数组的开头删除元素
numbers.shift();
- splice:在数组的任意位置添加或删除元素
numbers.splice(5,3);
删除了从数组索引 5 开始的 3 个元素numbers.splice(5,3);
在数组索引 5 之后插入 2,3,4 这 3 个元素
二维数组和多维数组
let averageTemp = [];
averageTemp[0] = [72, 75, 79, 79, 81, 81];
averageTemp[1] = [81, 79, 75, 75, 73, 73];
// 迭代二维数组中的元素
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]);
}
}
}
printMatrix(averageTemp);
创建一个 3×3×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;
}
}
}
输出上面矩阵中的内容
for (let i = 0; i < matrix3x3x3.length; i++) {
for (let j = 0; j < matrix3x3x3[i].length; j++) {
for (let z = 0; z < matrix3x3x3[i][j].length; z++) {
console.log(matrix3x3x3[i][j][z]);
}
}
}
常见数组方法参考
数组合并
concat 方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序连接指定数组
const zero = 0;
const positiveNumbers = [1, 2, 3];
const negativeNumbers = [-3, -2, -1];
let numbers = negativeNumbers.concat(zero, positiveNumbers); // -3、-2、-1、0、1、2、3
let numbers = negativeNumbers.concat(zero, 1, 1, 1); // -3、-2、-1、0、1、1、1
迭代器函数
function isEven(x) {
// 如果x是2 的倍数,就返回true
console.log(x);
return x % 2 === 0 ? true : false;
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
// 使用ES6改写方法
const isEven2 = (x) => x % 2 === 0;
every
every 方法会迭代数组中的每个元素,直到返回 false
numbers.every(isEven);
some
some 方法跟 every 的行为刚好相反,它会迭代数组中的每个元素,直到函数返回 false
numbers.some(isEven);
forEach
forEach 方法会迭代整个数组,结果与 for 循环相同
numbers.forEach((x) => console.log(x % 2 === 0));
map 和 filter 方法
map 方法会迭代整个数组,返回一个新数组,里面保存的是传入 map 方法的运行结果
const myMap = numbers.map(isEven); // [false, true, false, true, false, true,false, true, false, true, false, true, false, true, false]
filter 方法会迭代整个数组,返回一个新数组,里面保存的是传入 filter 方法的运行返回为 true 的元素
const evenNumbers = numbers.filter(isEven); // [2,4, 6, 8, 10, 12, 14]
reduce 方法
reduce 方法接收一个有如下四个参数的函数:previousValue、currentValue、index 和 array。因为 index 和 array 是可选的参数,所以如果用不到它们的话,可以不传。这个函数会返回一个将被叠加到累加器的值,reduce 方法停止执行后会返回这个累加器。如果要对一个数组中的所有元素求和,这就很有用。
numbers.reduce((previous, current) => previous + current);
ECMAScript 6 和数组的新功能
ES2015 和 ES2016 新增的数组方法
使用 for...of 循环迭代
for (const n of numbers) {
console.log(n % 2 === 0 ? "even" : "odd");
}
使用@@iterator 对象
ES2015 还为 Array 类增加了一个@@iterator 属性,需要通过 Symbol.iterator 来访问
let iterator = numbers[Symbol.iterator]();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
console.log(iterator.next().value); // 4
console.log(iterator.next().value); // 5
然后,不断调用迭代器的 next 方法,就能依次得到数组中的值。numbers 数组中有 15 个值,因此需要调用 15 次 iterator.next().value。
我们可以用下面的代码来输出 numbers 数组中的 15 个值。
iterator = numbers[Symbol.iterator]();
for (const n of iterator) {
console.log(n);
}
数组中的所有值都迭代完之后,iterator.next().value 会返回 undefined。
数组的 entries、keys 和 values 方法
entries 方法返回包含键值对的@@iterator,下面是使用该方法的代码示例
let aEntries = numbers.entries(); // 得到键值对的迭代器
console.log(aEntries.next().value); // [0, 1] - 位置0 的值为1
console.log(aEntries.next().value); // [1, 2] - 位置1 的值为2
console.log(aEntries.next().value); // [2, 3] - 位置2 的值为3
numbers 数组中都是数,key 是数组中的位置,value 是保存在数组索引的值。我们也可以使用下面的代码。
aEntries = numbers.entries();
for (const n of aEntries) {
console.log(n);
}
使用集合、字典、散列表等数据结构时,能够取出键值对是很有用的。
keys 方法返回包含数组索引的@@iterator,下面是使用该方法的代码示例
const aKeys = numbers.keys(); // 得到数组索引的迭代器
console.log(aKeys.next()); // {value: 0, done: false }
console.log(aKeys.next()); // {value: 1, done: false }
console.log(aKeys.next()); // {value: 2, done: false }
```
keys 方法会返回 numbers 数组的索引。一旦没有可迭代的值,aKeys.next()就会返回一个 value 属性为 undefined、done 属性为 true 的对象。如果 done 属性的值为 false,就意味着还有可迭代的值。
values 方法返回的@@iterator 则包含数组的值。使用这个方法的代码示例如下
const aValues = numbers.values();
console.log(aValues.next()); // {value: 1, done: false }
console.log(aValues.next()); // {value: 2, done: false }
console.log(aValues.next()); // {value: 3, done: false }
from 方法
Array.from 方法根据已有的数组创建一个新数组。比如,要复制 numbers 数组,可以如下这样做。
let numbers2 = Array.from(numbers);
// 还可以传入一个用来过滤值的函数,例子如下。
let evens = Array.from(numbers, (x) => x % 2 == 0);
使用 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];
// 我们也可以用该方法复制已有的数组,
let numbersCopy = Array.of(...numbers4);
使用 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的位置(包括3,不包括5),得到的数组为[0, 2, 2, 1,1, 2]。
创建数组并初始化值的时候,fill 方法非常好用,就像下面这样。
let ones = Array(6).fill(1); // 创建了一个长度为6、所有值都是1的数组([1, 1, 1, 1, 1, 1])。
使用 copyWithin 方法
copyWithin 方法复制数组中的一系列元素到同一数组指定的起始位置。看看下面这个例子。
let copyArray = [1, 2, 3, 4, 5, 6];
// 假如我们想把4、5、6三个值复制到数组前三个位置,得到[4, 5, 6, 4, 5, 6]这个数组,可以用下面的代码达到目的。
copyArray.copyWithin(0, 3);
// 假如我们想把4、5两个值(在位置3和4上)复制到位置1和2,可以这样做:
copyArray = [1, 2, 3, 4, 5, 6];
copyArray.copyWithin(1, 3, 5); // 这种情况下,会把从位置3开始到位置5结束(包括3,不包括5)的元素复制到位置1,结果是得到数组[1, 4, 5, 4, 5, 6]。
排序元素
- reverse:数组反转
- sort:数组排序
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
numbers.reverse(); // [15, 14, 13, 12, 11, 10, 9, 8,7, 6, 5, 4, 3, 2, 1]
numbers.sort(); // [1, 10, 11, 12, 13, 14, 15, 2, 3,4, 5, 6, 7, 8, 9]
看起来不大对,是吧?这是因为 sort 方法在对数组做排序时,把元素默认成字符串进行相互比较。
我们可以传入自己写的比较函数。因为数组里都是数,所以可以像下面这样写。
numbers.sort((a, b) => a - b);
在 b 大于 a 时,这段代码会返回负数,反之则返回正数。如果相等的话,就会返回 0。也就是说返回的是负数,就说明 a 比 b 小,这样 sort 就能根据返回值的情况对数组进行排序。
之前的代码也可以表示成如下这样,会更清晰一些。
function compare(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
// a必须等于b
return 0;
}
numbers.sort(compare);
这是因为 JavaScript 的 sort 方法接收 compareFunction 作为参数,然后 sort 会用它排序数组。在这个例子里,我们声明了一个用来比较数组元素的函数,使数组按升序排序。
自定义排序
我们可以对任何对象类型的数组排序,也可以创建 compareFunction 来比较元素。例如,对象 Person 有名字和年龄属性,我们希望根据年龄排序,就可以这么写。
const friends = [
{ name: "John", age: 30 },
{ name: "Ana", age: 20 },
{ name: "Chris", age: 25 }, // ES2017 允许存在尾逗号
];
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));
在这个例子里,最后会输出 Ana(20), Chris(25), John(30)。
字符串排序
假如有这样一个数组。你猜会输出什么?
let names = ["Ana", "ana", "john", "John"];
console.log(names.sort()); // ["Ana", "John", "ana", "john"]
既然 a 在字母表里排第一位,为何 ana 却排在了 John 之后呢?这是因为 JavaScript 在做字符比较的时候,是根据字符对应的 ASCII 值来比较的。例如,A、J、a、j 对应的 ASCII 值分别是 65、74、97、106。
虽然 a 在字母表里是最靠前的,但 J 的 ASCII 值比 a 的小,所以排在了 a 前面。
现在,如果给 sort 传入一个忽略大小写的比较函数,将输出["Ana", "ana", "John","john"]。
names = ["Ana", "ana", "john", "John"]; // 重置数组的初始状态
console.log(
names.sort((a, b) => {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
}
if (a.toLowerCase() > b.toLowerCase()) {
return 1;
}
return 0;
})
); // ['Ana', 'ana', 'john', 'John']
在这种情况下,sort 函数不会有任何作用。它会按照现在的大小写字母顺序排序。
如果希望小写字母排在前面,那么需要使用 localeCompare 方法。
names.sort((a, b) => a.localeCompare(b));
输出结果将是["ana", "Ana", "john", "John"]。
假如对带有重音符号的字符做排序的话,也可以用 localeCompare 来实现。
const names2 = ["Maève", "Maeve"];
console.log(names2.sort((a, b) => a.localeCompare(b))); // ["Maeve", "Maève"];
搜索
- indexOf 方法返回与参数匹配的第一个元素的索引;
- lastIndexOf 返回与参数匹配的最后一个元素的索引
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
console.log(numbers.indexOf(10)); // 9
console.log(numbers.indexOf(100)); // -1 (100不在数组中)
numbers.push(10);
console.log(numbers.lastIndexOf(10)); // 15,此时数组中元素为[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10]
console.log(numbers.lastIndexOf(100)); // -1(100不在数组中)
ECMAScript 2015——find 和 findIndex 方法
find 和 findIndex 方法接收一个回调函数,搜索一个满足回调函数条件的值。
find 和 findIndex 的不同之处在于,find 方法返回第一个满足条件的值,findIndex 方法则返回这个值在数组里的索引。如果没有满足条件的值,find 会返回 undefined,而 findIndex 返回-1。
// 从数组里找一个13的倍数。
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
function multipleOf13(element, index, array) {
return element % 13 == 0;
}
console.log(numbers.find(multipleOf13)); // 13
console.log(numbers.findIndex(multipleOf13)); // 12
ECMAScript 7——使用 includes 方法
如果数组里存在某个元素,includes 方法会返回 true,否则返回 false。使用 includes 方法的例子如下。
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
console.log(numbers.includes(15)); // true
console.log(numbers.includes(20)); // false
如果给 includes 方法传入一个起始索引,搜索会从索引指定的位置开始。
let numbers2 = [7, 6, 5, 4, 3, 2, 1];
console.log(numbers2.includes(4, 5)); // false 数组索引5之后的元素不包含4。
输出数组为字符串
如果想把数组里所有元素输出为一个字符串,可以用 toString 方法。
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
console.log(numbers.toString()); // 1、2、3、4、5、6、7、8、9、10、11、12、13、14、15
如果想用一个不同的分隔符(比如-)把元素隔开,可以用 join 方法。
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const numbersString = numbers.join("-");
console.log(numbersString); // 1-2-3-4-5-6-7-8-9-10-11-12-13-14-15
类型数组
与 C 和 Java 等其他语言不同,JavaScript 数组不是强类型的,因此它可以存储任意类型的数据。
类型数组则用于存储单一类型的数据。它的语法是 let myArray = new TypedArray (length),其中 TypedArray 需替换为下表所列之一。
let length = 5;
let int16 = new Int16Array(length);
let array16 = [];
array16.length = length;
for (let i = 0; i < length; i++) {
int16[i] = i + 1;
}
console.log(int16);
使用 WebGL API、进行位操作、处理文件和图像时,类型数组都可以大展拳脚。它用起来和普通数组毫无二致,本章所学的数组方法和功能都可以用于类型数组。https://www.html5rocks.com/en/tutorials/webgl/typed_arrays/ 是一个很好的教程,讲解了如何使用类型数组处理二进制数据,以及它在实际项目中的应用。
TypeScript 中的数组
TypeScript 会在编译时进行类型检测,来确保只对所有值都属于相同数据类型的数组进行操作。
如果检查下面这段代码,会发现它和本章前几节声明的 numbers 数组是一样的。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
根据类型推断,TypeScript 能够理解 numbers 数组的声明和 const numbers: number[]是一样的。出于这个原因,如果我们在声明时给变量赋了初始值,就不需要每次都显式声明变量的类型了。
回到对 friends 数组的排序示例,我们可以用 TypeScript 将代码重构成如下这样。
interface Person {
name: string;
age: number;
}
// const friends: {name: string, age: number}[];
const friends = [
{ name: "John", age: 30 },
{ name: "Ana", age: 20 },
{ name: "Chris", age: 25 },
];
function comparePerson(a: Person, b: Person) {
// comparePerson函数的内容
}
通过声明 Person 接口,我们确保了 comparePerson 函数只接收包含 name 和 age 属性的对象。friends 数组没有显式的类型,因此可以在本例中通过 const friends: Person[]显式声明它的类型。
总之,如果想用 TypeScript 给 JavaScript 变量设置类型,我们只需要使用 const
或let variableName: <type>[]
,抑或像我们在第 1 章中学习到的,在使用.js 扩展名的文件时,在第一行添加注释 // @ts-check
。
在运行时,输出结果和使用纯 JavaScript 时是一样的。