3.2 创建和初始化数组
用 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})。
只用中括号([])的形式创建一个数组,如下所示。
let daysOfWeek = [];
使用一些元素初始化数组,如下所示。
let daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday','Friday', 'Saturday'];
使用数组的 length 属性可以知道数组里已经存了多少个元素(它的大小)。
console.log(daysOfWeek.length);
访问元素和迭代数组
访问数组里特定位置的元素,可以用中括号传递数值位置,得到想知道的值或者赋新的值。
想输出数组 daysOfWeek 里的所有元素,可以通过循环迭代数组、打印元素,如 下所示。
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}
}
在行{1}处,我们声明并创建了一个数组。
在行{2}和行{3},把斐波那契数列中的前两个数分别赋给了数组的第二和第三个位置。
数组第一位的索引始终是 0。因为斐波那契数列中不存在 0,所以这里直接略过,从第二位开始分别保存斐波那契数列中对应位置的元素。
为了得到斐波那契数列中第三到第二十个位置上的数,可以用循环来处理,把数组中前两位上的元素相加,结果赋给当前位置上的元素(行{4}——从数组中的索引 3 到索引 19)。
看看输出(行{6}),只需要循环迭代数组的各个元素(行{5})。
3.3 添加元素
let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
3.3.1 在数组末尾插入元素
如果想要给数组添加一个元素(比如 10),只要把值赋给数组中最后一个空位上的元素即可。
numbers[numbers.length] = 10;
使用 push 方法
push 方法,能把元素添加到数组的末尾。
numbers.push(11);
numbers.push(12, 13);
3.3.2 在数组开头插入元素
在数组中插入一个新元素(数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 方法
unshift,可以直接把数值插入数组的开头
numbers.unshift(-2);
numbers.unshift(-4, -3);
3.4.1 从数组末尾删除元素
要删除数组里最靠后的元素,可以用 pop 方法。
numbers.pop();
3.4.2 从数组开头删除元素
要移除数组里的第一个元素,可以用下面的代码。
for (let i = 0; i < numbers.length; i++) {
numbers[i] = numbers[i + 1];
}
下图呈现了这段代码的执行过程。
数组里所有的元素都左移了一位,但数组的长度依然是 17,在最后一次循环里,i+1 引用了数组里还未初始化的一个位置。
我们只是把数组第一位的值用第二位覆盖了,并没有删除元素。
创建一个新的数组,将所有不是 undefined 的值从原来的数组复制到新的数组中,并且将这个新的数组赋值给我们的数组。
Array.prototype.reIndex = function(myArray) {
const newArray = [];
for(let i = 0; i < myArray.length; i++ ) {
if (myArray[i] !== undefined) {
// console.log(myArray[i]);
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 方法实现。
numbers.shift();
假如本来数组中的值是从4 到 12,长度为 17。执行了上述代码后,数组就只有3 到 12 了, 长度也会减小到 16。
3.5 在任意位置添加或删除元素
使用 splice 方法,简单地通过指定位置/索引,就可以删除相应位置上指定数量 的元素。
numbers.splice(5,3);
这行代码删除了从数组索引 5 开始的 3 个元素。这就意味着 numbers[5]、numbers[6]和 numbers[7]从数组中删除了。
用 delete 运算符删除数组中的元素, 例如 delete numbers[0]。数组位置 0 的值会变成 undefined,也就 是说,以上操作等同于 numbers[0] = undefined。
把数 2、3、4 插入数组里,放到之前删除元素的位置上,可以再次使用 splice 方法。
numbers.splice(5, 0, 2, 3, 4);
splice 方法接收的第一个参数,表示想要删除或插入的元素的索引值。第二个参数是删除元素的个数(这个例子里,我们的目的不是删除元素,所以传入 0)。第三个参数往后,就是要 添加到数组里的值(元素 2、3、4)。
以下代码的含义是从索引 5 开始删除了 3 个元素,再从索引 5 开始添加了元素 2、3、4。。
numbers.splice(5, 3, 2, 3, 4);
3.6 二维和多维数组
用数组套数组,实现矩阵或任一多维数组。
let averageTemp = [];
averageTemp[0] = [72, 75, 79, 79, 81, 81];
averageTemp[1] = [81, 79, 75, 75, 73, 73];
数组中的内容如下图所示。
3.6.1 迭代二维数组的元素
如果想看上面那个数组可以使用如下代码。
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]);
}
}
}
使用一个嵌套的 for 循环来处理,其中变量 i 为行, 变量 j 为列。每个 myMatrix[i]同样代表一个数组,因此需要在嵌套的 for 循 环中迭代 myMatrix[i]的每个位置。
可以使用以下代码来输出矩阵 averageTemp 的内容。
printMatrix(averageTemp);
3.6.2 多维数组
创建一个 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;
}
}
}
3 × 3 × 3 的矩阵立体图如下所示。
用以下代码输出这个矩阵的内容。
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]);
}
}
}
3.7 JavaScript 的数组方法参考
下表详述了数组的一些核心方法
方法 | 描述 |
---|---|
concat | 连接 2 个或更多数组,并返回结果 |
every | 对数组中的每个元素运行给定函数,如果该函数对每个元素都返回 true,则返回 true |
filter | 对数组中的每个元素运行给定函数,返回该函数会返回 true 的元素组成的数组 |
forEach | 对数组中的每个元素运行给定函数。这个方法没有返回值 |
join | 将所有的数组元素连接成一个字符串 |
indexOf | 返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1 |
lastIndexOf | 返回在数组中搜索到的与给定参数相等的元素的索引里最大的值 |
map | 对数组中的每个元素运行给定函数,返回每次函数调用的结果组成的数组 |
reverse | 颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在 的第一个 |
slice | 传入索引值,将数组里对应索引范围内的元素作为新数组返回 |
some | 对数组中的每个元素运行给定函数,如果任一元素返回 true,则返回 true |
sort | 按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数 |
toString | 将数组作为字符串返回 |
valueOf | 和 toString 类似,将数组作为字符串返回 |
3.7.1 数组合并
使用concat方法将多个数组,需要合并起来成为一个数组。
const zero = 0;
const positiveNumbers = [1, 2, 3];
const negativeNumbers = [-3, -2, -1];
let numbers = negativeNumbers.concat(zero, positiveNumbers);
concat 方法可以向一个数组传递数组、对象或是元素。数组会按照该方法传入的参数顺序 连接指定数组。
3.7.2 迭代器函数
假设数组中的值是从 1 到 15;如果数组里的元素可以被 2 整除(偶数),函数就返回 true, 否则返回 false。
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];
1. 用 every 方法迭代
every 方法会迭代数组中的每个元素,直到返回 false。
numbers.every(isEven);
在这个例子里,数组 numbers 的第一个元素是 1,它不是 2 的倍数(1 是奇数),因此 isEven 函数返回 false,然后 every 执行结束。
2. 用 some 方法迭代
some 方法会迭代数组的每个元素,直到函 数返回 true。
numbers.some(isEven);
这个例子里第一个被迭代的元素 是 1,isEven 会返回 false。第二个被迭代的元素是 2,isEven 返回 true——迭代结束。
3. 用 forEach 方法迭代
如果要迭代整个数组,可以用 forEach 方法。它和使用 for 循环的结果相同。
numbers.forEach(x => console.log(x % 2 === 0));
4. 使用 map 和 filter 方法
map方法返回新数组
const myMap = numbers.map(isEven);
数组 myMap 里的值是:[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]。它保存了传入 map 方法的 isEven 函数的运行结果。
filter 方法,它返回的新数组由使函数返回 true 的元素组成。
const evenNumbers = numbers.filter(isEven);
这个例子里,evenNumbers 数组中的元素都是偶数:[2, 4, 6, 8, 10, 12, 14]。
5. 使用 reduce 方法
reduce 方法接收一个有如下四个参数的函数:previousValue、 currentValue、index 和 array。这个函数会返回一个将被叠加到累加器的值,reduce 方法停止执行后会返回 这个累加器。
如果要对一个数组中的所有元素求和,可以参考下面这个例子。
numbers.reduce((previous, current) => previous + current);
3.7.3 ECMAScript 6 和数组的新功能
下表列出了 ES2015 和 ES2016 新增的数组方法。
方法 | 描述 |
---|---|
@@iterator | 返回一个包含数组键值对的迭代器对象,可以通过同步调用得到数组元素的键值对 |
copyWithin | 复制数组中一系列元素到同一数组指定的起始位置 |
entries | 返回包含数组所有键值对的@@iterator |
includes | 如果数组中存在某个元素则返回 true,否则返回 false。E2016 新增 |
find | 根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素 |
findIndex | 根据回调函数给定的条件从数组中查找元素,如果找到则返回该元素在数组中的索引 |
fill | 用静态值填充数组 |
from | 根据已有数组创建一个新数组 |
keys | 返回包含数组所有索引的@@iterator |
of | 根据传入的参数创建一个新数组 |
values | 返回包含数组中所有值的@@iterator |
1. 使用 for...of 循环迭代
for (const n of numbers) {
console.log(n % 2 === 0 ? 'even' : 'odd');
}
2. 使用@@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 个值。
iterator = numbers[Symbol.iterator]();
for (const n of iterator) {
console.log(n);
}
数组中的所有值都迭代完之后,iterator.next().value 会返回 undefined。
3. 数组的 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 }
4. 使用 from 方法
Array.from 方法根据已有的数组创建一个新数组。
要复制 numbers 数组,可以如 下这样做。
let numbers2 = Array.from(numbers);
还可以传入一个用来过滤值的函数,例子如下。
let evens = Array.from(numbers, x => (x % 2 == 0));
上面的代码会创建一个 evens 数组,以及值 true(如果在原数组中为偶数)或 false(如 果在原数组中为奇数)。
5. 使用 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);
上面的代码和 Array.from(numbers4)的效果是一样的。
6. 使用 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])。
7. 使用 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 两个值复制到位置 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]。
3.7.4 排序元素
首先,我们想反序输出数组 numbers(它本来的排序是 1, 2, 3, 4, ..., 15)。要实现 这样的功能,可以用 reverse 方法,然后数组内元素就会反序。
numbers.reverse();
现在,输出 numbers 的话就会看到[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]。然后,我们使用 sort 方法。
numbers.sort();
然而,如果输出数组,结果会是[1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9]。因为 sort 方法在对数组做排序时,把元素默认成字符串进行相 互比较。
可以传入自定义的比较函数.
numbers.sort((a, b) => a - b);
之前的代码可以改成下面这个样子。
function compare(a, b) {
if (a < b) {
return -1;//如果返回-1,那么第一个参数a出现在第二个参数b的前面
}
if (a > b) {
return 1;//如果返回1,那么第一个参数a出现在第二个参数b的后面
}
// a 必须等于 b
return 0;
}
numbers.sort(compare);
1. 自定义排序
例如, 对象 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)。
2. 字符串排序
let names = ['Ana', 'ana', 'john', 'John'];
console.log(names.sort());
因为 JavaScript 在做字 符比较的时候,是根据字符对应的 ASCII 值来比较的。例如,A、J、a、j 对应的 ASCII 值分别 是 65、74、97、106。所以答案如下所示。
["Ana", "John", "ana", "john"]
如果给 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;
}));
如果希望小写字母排在前面,那么需要使用 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"]。
1. ECMAScript 2015——find 和 findIndex 方法
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));
console.log(numbers.findIndex(multipleOf13));
find 和 findIndex 方法接收一个回调函数,搜索一个满足回调函数条件的值。上面的例子 里,我们要从数组里找一个 13 的倍数。
find 方法返回第一个满足条件的值,findIndex 方法则返回这个值在数组里的索引。 如果没有满足条件的值, find 会返回 undefined , 而 findIndex 返回-1。
2. ECMAScript 7——使用 includes 方法
如果数组里存在某个元素,includes 方法会返回 true,否则返回 false。
console.log(numbers.includes(15));
console.log(numbers.includes(20));
例子里的 includes(15)返回 true,includes(20)返回 false,因为 numbers 数组里没 有 20。
如果给 includes 方法传入一个起始索引,搜索会从索引指定的位置开始。
let numbers2 = [7,6,5,4,3,2,1];
console.log(numbers2.includes(4,5));
上面的例子输出为 false,因为数组索引 5 之后的元素不包含 4。
3.7.6 输出数组为字符串
如果想把数组里所有元素输出为一个字符串,可以用 toString 方法。
console.log(numbers.toString());
如果想用一个不同的分隔符(比如 )把元素隔开,可以用 join 方法。
const numbersString = numbers.join('-');
console.log(numbersString);
3.8 类型数组
类型数组则用于存储单一类型的数据。 它的语法是 let myArray = new TypedArray (length),其中 TypedArray 需替换为下表所列之一。
类型数组 | 数据类型 |
---|---|
Int8Array | 8 位二进制补码整数 |
Uint8Array | 8 位无符号整数 |
Uint8ClampedArray | 8 位无符号整数 |
Int16Array | 16 位二进制补码整数 |
Uint16Array | 16 位无符号整数 |
Int32Array | 32 位二进制补码整数 |
Uint32Array | 32 位无符号整数 |
Float32Array | 32 位 IEEE 浮点数 |
Float64Array | 64 位 IEEE 浮点数 |
代码示例如下。
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、进行位操作、处理文件和图像时,类型数组都可以大展拳脚。
3.9 TypeScript 中的数组
对 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: