数组
数组
数组扩展了对象,提供了特殊的方法来处理有序的数据集合以及 length 属性。但从本质上讲,它仍然是一个对象。但是数组真正特殊的是它的内部实现。JavaScript 引擎尝试把这些元素一个接一个地存储在连续的内存区域,而且还有一些其它的优化,以使数组更好地作用于连续的有序数据。如果我们不像“有序集合”那样使用数组,而是像常规对象那样使用数组,从技术上讲,这是可以的,因为数组是基于对象的,但是这些优化就都不生效了。
let fruits = []; // 创建一个数组
// 以下操作不会报错,但是Javascript 引擎会发现,我们在像使用常规对象一样使用数组,那么针对数组的优化就不再适用了
fruits[99999] = 5; // 分配索引远大于数组长度的属性
fruits.age = 25; // 创建一个具有任意名称的属性数组是存储有序数据的集合。数组元素从 0 开始编号。数组可以存储任何类型的元素。
// 混合值
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
// 获取索引为 1 的对象然后显示它的 name
alert( arr[1].name ); // John
// 获取索引为 3 的函数并执行
arr[3](); // hellolength
属性不是数组里元素的个数,而是最大的数字索引值加一。let fruits = [];
fruits[123] = "Apple";
console.log( fruits.length ); // 124
// 查找数组最中间的元素(要求适用于任何奇数长度的数组)
arr[Math.floor((arr.length - 1) / 2)]length
属性是可写的。如果我们减少它,数组就会被截断,该过程是不可逆的。所以,清空数组最简单的方法就是:arr.length = 0;
。let arr = [1, 2, 3, 4, 5];
arr.length = 2; // 截断到只剩 2 个元素
console.log( arr ); // [1, 2]
arr.length = 5; // 又把 length 加回来
console.log( arr[3] ); // undefined:被截断的那些数值并没有回来如果使用单个参数(即数字)调用
new Array
,那么它会创建一个 指定了长度,却没有任何项 的数组。let arr = new Array("Apple", "Pear", "etc");
let arr = new Array(2);
alert( arr[0] ); // undefined!没有元素。
alert( arr.length ); // length 2数组里的项也可以是数组。我们可以将其用于多维数组,例如存储矩阵:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // 最中间的那个数在数组上下文调用
Array.prototype.at()
接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。
- 如果
i >= 0
,则arr.at(i)
与arr[i]
完全相同。 - 如果
i
为负数,则从数组的尾部向前数。
let fruits = ["Apple", "Orange", "Plum"];
// 与 fruits[fruits.length-1] 相同
alert( fruits.at(-1) ); // Plum
Array.prototype[@@iterator]()
warning
数组迭代器对象应该是一次性使用的对象。不要重复使用它。
Array 实例的
[@@iterator]()
方法实现了迭代协议,允许数组被大多数期望可迭代对象的语法所使用,例如 展开语法(spread syntax ) 和 for..of 循环。它返回一个数组迭代器对象,该对象会产生数组中每个索引的值。实际使用语法:
array[Symbol.iterator]()
。请注意,你很少需要直接调用此方法。@@iterator
方法的存在使数组可迭代,并且像 for..of 循环这样的迭代语法会自动调用此方法以获得要遍历的迭代器。如果你需要更多的控制迭代过程,你可以手动调用返回的迭代器对象的
next()
方法。
values 方法
values
方法返回一个新的数组迭代器对象,该对象迭代数组中每个元素的值。Array.prototype.values()
是Array.prototype[@@iterator]()
的默认实现。values
方法返回的可迭代对象是不可重复使用的。
pop/push, shift/unshift 方法
JavaScript 中的数组既可以用作队列(First-In-First-Out),也可以用作栈(Last-In-First-Out)。它们允许你从首端/末端来添加/删除元素。双端队列
pop
取出并返回数组的最后一个元素let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.pop() ); // 移除 "Pear" 然后 alert 显示出来
alert( fruits ); // Apple, Orangepush
在数组末端添加元素。调用fruits.push(...)
与fruits[fruits.length] = ...
是一样的。shift
取出数组的第一个元素并返回它let fruits = ["Apple", "Orange", "Pear"];
alert( fruits.shift() ); // 移除 Apple 然后 alert 显示出来
alert( fruits ); // Orange, Pearunshift
在数组的首端添加元素。push
和unshift
方法都可以一次添加多个元素let fruits = ["Apple"];
fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );push/pop 方法运行的比较快,而 shift/unshift 比较慢。
splice/slice 方法
warning
splice 方法 修改的是数组本身。
如何从数组中删除元素?数组是对象,所以我们可以尝试使用
delete
,如下发现元素被删除了,但数组仍然有 3 个元素。这是因为delete obj.key
是通过key
来移除对应的值。对于普通对象来说是可以的。但是对于数组来说,我们通常希望剩下的元素能够移动并占据被释放的位置。arr.splice(start[, deleteCount, elem1, ..., elemN])
可以添加、删除和插入元素。它从索引start
开始修改arr
:删除deleteCount
个元素并在当前位置插入elem1, ..., elemN
,最后返回被删除的元素所组成的数组。(当只填写了 splice 的start
参数时,将删除从索引start
开始的所有数组项)let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // 从索引 1 开始删除 1 个元素
alert( arr ); // ["I", "JavaScript"]let arr = ["I", "study", "JavaScript", "right", "now"];
// 删除数组的前三项,并使用其他内容代替它们
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // 现在 ["Let's", "dance", "right", "now"]// splice 返回被删除的元素所组成的数组
let arr = ["I", "study", "JavaScript", "right", "now"];
// 删除前两个元素
let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- 被从数组中删除了的元素// 将 deleteCount 设置为 0,splice 方法就能够插入元素而不用删除任何元素
let arr = ["I", "study", "JavaScript"];
// 从索引 2 开始
// 删除 0 个元素
// 然后插入 "complex" 和 "language"
arr.splice(2, 0, "complex", "language");
alert( arr ); // "I", "study", "complex", "language", "JavaScript"// 允许负向索引
let arr = [1, 2, 5];
// 从索引 -1(尾端前一位)
// 删除 0 个元素,
// 然后插入 3 和 4
arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5arr.slice([start], [end])
创建一个新数组,将所有从索引start
到end
(不包括end
)的数组项复制到一个新的数组。start
和end
都可以是负数,在这种情况下,从末尾计算索引。我们也可以不带参数地调用它:arr.slice()
会创建一个arr
的副本。其通常用于获取副本,以进行不影响原始数组的进一步转换。let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素)
alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)
concat 方法
arr.concat(arg1, arg2...)
创建一个新数组,其中包含来自于arr
,然后是arg1
,arg2
的元素。它接受任意数量的参数(数组或值都可以,如果参数是一个数组,那么其中的所有元素都会被复制,否则,将复制参数本身)。let arr = [1, 2];
// 从 arr 和 [3,4] 创建一个新数组
alert( arr.concat([3, 4]) ); // 1,2,3,4
// 从 arr、[3,4] 和 [5,6] 创建一个新数组
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
// 从 arr、[3,4]、5 和 6 创建一个新数组
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6concat的参数如果是个类数组对象,通常会被作为一个整体添加。但是,如果类数组对象具有
Symbol.isConcatSpreadable
属性,那么它就会被 concat 当作一个数组来处理:此对象中的元素将被添加。let arr = [1, 2];
let arrayLike = {
0: "something",
length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
indexOf/lastIndexOf 和 includes 方法
arr.indexOf(item, from)
从索引from
开始搜索item
,如果找到则返回索引,否则返回 -1。默认情况下,搜索是从头开始的。arr.includes(item, from)
从索引from
开始搜索item
,如果找到则返回 true,否则返回 false。默认情况下,搜索是从头开始的。方法
lastIndexOf
与indexOf
相似,但从右向左查找。
let arr = [1, 0, false];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // true
let fruits = ['Apple', 'Orange', 'Apple'];
alert( fruits.indexOf('Apple') ); // 0(第一个 Apple)
alert( fruits.lastIndexOf('Apple') ); // 2(最后一个 Apple)
tip
indexOf
和includes
使用严格相等===
进行比较。方法
includes
可以正确地处理NaN
,而indexOf
不能
find 和 findIndex/findLastIndex
arr.find(function(item, index, array) {...})
依次对数组中的每个元素调用该函数,如果该函数返回 true,则搜索停止,并返回item
。对于假值(falsy)的情况,则返回undefined
。let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
let user = users.find(item => item.id == 1);
alert(user.name); // JohnfindIndex
方法与find
具有相同的语法,但它返回找到的元素的索引,而不是元素本身。如果没找到,则返回 -1。findLastIndex
方法类似于findIndex
,但从右向左搜索let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"},
{id: 4, name: "John"}
];
// 寻找第一个 John 的索引
alert(users.findIndex(user => user.name == 'John')); // 0
// 寻找最后一个 John 的索引
alert(users.findLastIndex(user => user.name == 'John')); // 3
filter 方法
返回的是所有匹配元素组成的数组。对于假值(falsy)的情况,则返回空数组。
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
// 返回前两个用户的数组,回调函数使用箭头函数
let someUsers = users.filter(item => item.id < 3);
alert(someUsers.length); // 2
map 方法
对数组的每个元素都调用函数,并返回结果数组。
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6
let john = { name: "John", surname: "Smith", id: 1 };
let pete = { name: "Pete", surname: "Hunt", id: 2 };
let mary = { name: "Mary", surname: "Key", id: 3 };
let users = [ john, pete, mary ];
let usersMapped = users.map(user => ({ // 把要返回的对象直接包在括号里,这样可以省略写return
fullName: `${user.name} ${user.surname}`,
id: user.id
}));
/*
usersMapped = [
{ fullName: "John Smith", id: 1 },
{ fullName: "Pete Hunt", id: 2 },
{ fullName: "Mary Key", id: 3 }
]
*/
alert( usersMapped[0].id ); // 1
alert( usersMapped[0].fullName ); // John Smith
sort 方法
warning
sort 方法 修改的是数组本身。
对数组进行 原位(in-place,是指在此数组内,而非生成一个新数组)排序,更改元素的顺序(默认情况下按字符串进行排序)。
要使用我们自己的排序顺序,我们需要提供一个函数作为
sort()
的参数。比较函数只需要返回一个正数表示“大于”,一个负数表示“小于”。
对于许多字母,最好使用
localeCompare
方法正确地对字母进行排序let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich(错的)
alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam(对的!)
reverse 方法
warning
reverse 方法 修改的是数组本身。
reverse()
方法就地反转数组中的元素,并返回同一数组的引用。要在不改变原始数组的情况下反转数组中的元素,使用 toReversed()
。
const array1 = ['one', 'two', 'three'];
const reversed = array1.reverse();
console.log('reversed:', reversed); // "reversed:" Array ["three", "two", "one"]
console.log('array1:', array1); // "array1:" Array ["three", "two", "one"]
split 和 join
split(delim)
方法通过给定的分隔符delim
将字符串分割成一个数组。split
方法有一个可选的第二个数字参数用于对数组长度进行限制。如果提供了,那么额外的元素会被忽略。若split
方法参数为空字符串,则会将字符串拆分为字母数组。let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字)
}
// 限制数组长度
let arr2 = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
console.log( arr2 ); // ['Bilbo', 'Gandalf']
// 参数为空字符串,会将字符串拆分为字母数组
let str = "test";
alert( str.split('') ); // t,e,s,tjoin() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,用逗号(默认)或指定的分隔符分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。
const elements = ['Fire', 'Air', 'Water'];
console.log(elements.join()); // "Fire,Air,Water"
console.log(elements.join('')); // "FireAirWater"
reduce/reduceRight
reduce(callbackFn, [initialValue])
,callbackFn
为数组中每个元素执行的函数。其返回值将作为下一次调用callbackFn
时的accumulator
参数。对于最后一次调用,返回值将作为reduce
方法 的返回值。let value = array.reduce(function(accumulator, currentValue, currentIndex, array) {
// ...
}, [initialValue]);
/*
accumulator —— 是上一个函数调用的结果,第一次等于 initial(如果提供了 initialValue 的话)。
currentValue —— 当前的数组元素。
currentIndex —— 当前索引。
array —— 调用了 reduce方法 的数组本身。
*/如果没有初始值,那么
reduce
会将数组的第一个元素作为初始值,并从第二个元素开始迭代。如果初始值存在,空数组的reduce运算结果为该初始值;否则对空数组使用没有初始值的reduce会报错。
reduceRight
和reduce
方法的功能一样,只是遍历为从右到左。
some 和 every
some(callbackFn, [thisArg])
是一个迭代方法。它为数组中的每个元素调用一次指定的callbackFn
函数,直到callbackFn
返回一个真值。如果找到这样的元素,some
方法将会立即返回 true 并停止遍历数组。否则,如果callbackFn
对所有元素都返回假值,some
就会返回 false。every(callbackFn, [thisArg])
是一个迭代方法。它为数组中的每个元素调用一次指定的callbackFn
函数,直到callbackFn
返回一个假值。如果找到这样的元素,every
方法将会立即返回 false 并停止遍历数组。否则,如果callbackFn
为每个元素返回一个真值,every
就会返回 true。// 可以使用every方法比较简单数组
function arraysEqual(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
}
alert( arraysEqual([1, 2], [1, 2])); // true
fill
fill(value, [start], [end])
用一个固定值填充一个数组中从start(默认为 0)到end(默认为 array.length)内的全部元素。返回修改后的数组。
copyWithin
copyWithin(target, [start], [end])
浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。
flat/flatMap
从多维数组创建一个新的扁平数组
forEach
Array.prototype.forEach()
允许为数组的每个元素都运行一个函数(该函数的结果(如果它有返回)会被抛弃和忽略)。
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
Array.isArray
Array.isArray(value)
如果 value
是一个数组,则返回 true;否则返回 false。
alert(Array.isArray({})); // false
alert(Array.isArray([])); // true
alert(typeof {}); // object
alert(typeof []); // object(相同)
Array.of
Array.of(element0, element1, /* … ,*/ elementN)
通过可变数量的参数创建一个新的 Array 实例,而不考虑参数的数量或类型。
console.log( Array.of('foo', 2, 'bar', true) ); // ["foo", 2, "bar", true]
console.log( Array.of() ); // []
Array.from
Array.from(arrayLike, [mapFn], [thisArg])
从可迭代或类数组对象创建一个新的浅拷贝的数组实例。
console.log(Array.from('foo')); // ["f", "o", "o"]
console.log(Array.from([1, 2, 3], (x) => x + x)); // [2, 4, 6]
数组方法的thisArg
参数
许多数组方法接受
thisArg
作为可选的最后一个参数,例如find(callbackFn, thisArg)
,filter(callbackFn, thisArg)
,map(callbackFn, thisArg)
等thisArg
参数(默认为undefined
)将在调用callbackFn
时用作this
值。对于使用 箭头函数 定义的任何callbackFn
来说,thisArg
参数都是无关紧要的,因为箭头函数没有自己的this
绑定。
数组的转换
数组没有
Symbol.toPrimitive
,也没有valueOf
,它们只能执行toString
进行转换。 数组的toString
方法,会返回以逗号隔开的元素列表。不要使用
==
比较数组,该运算符不会对数组进行特殊处理,它会像处理任意对象那样处理数组。所以,如果我们使用==
来比较数组,除非我们比较的是两个引用同一数组的变量,否则它们永远不相等。alert( [] == [] ); // false
alert( [0] == [0] ); // false数组和原始类型进行比较时,数组会被转换为原始类型以进行比较,比如
[]
会被转换为一个空字符串 ''alert( 0 == [] ); // true 数组(对象)与原始类型比较先被转换为原始类型([]会被转换为一个空字符串 ''),不同原始类型比较会被转换为Number类型以进行比较('' 被转换成了数字 0)
alert('0' == [] ); // false