添加/移除首端或尾端数组元素
从数组的首端或尾端添加和删除元素的方法:
-
arr.push(...items)
—— 从尾端添加元素, -
arr.pop()
—— 从尾端提取元素, -
arr.shift()
—— 从首端提取元素, -
arr.unshift(...items)
—— 从首端添加元素。
从数组中删除元素
因为数组是对象,所以可以使用 delete
:
let arr = ["I", "go", "home"];
delete arr[1]; // remove "go"
alert( arr[1] ); // undefined
// now arr = ["I", , "home"];
alert( arr.length ); // 3
元素被删除了,但数组仍然有 3 个元素,因为 delete obj.key
是通过 key
来移除对应的值,被释放的位置没有被占据,如果想让数组空位置被占据,就要使用数组的方法。
splice(添加,删除和插入)
arr.splice(str)可以添加,删除和插入元素
语法:
arr.splice(index[, deleteCount, elem1, ..., elemN])
从 index
开始:删除 deleteCount
个元素并在当前位置插入 elem1, ..., elemN
。最后返回已删除元素的数组。
只有index
时会删除从当前索引开始的所有元素
例子:
- 从索引 1 开始删除 1 个元素
let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // 从索引 1 开始删除 1 个元素
alert( arr ); // ["I", "JavaScript"]
- 从头删除三个元素并替换
let arr = ["I", "study", "JavaScript", "right", "now"];
// remove 3 first elements and replace them with another
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // now ["Let's", "dance", "right", "now"]
- 返回已删除元素的数组
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"
---------------------------------------------------------------------
// 也可以使用负向索引
arr.splice(-1, 0,"complex", "language");
alert( arr ); // "I", "study", "complex", "language", "JavaScript"
slice(返回新数组)
arr.slice 方法比 arr.splice
简单得多,就是根据两个(开始、结束)索引返回新数组。
语法:
arr.slice([start], [end])
它会返回一个新数组,将所有从索引 start
到 end
(不包括 end
)的数组项复制到一个新的数组。start
和 end
都可以是负数,在这种情况下,从末尾计算索引。
只有start
时,返回从位置start
到尾端的的数组
例子:
let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素)
alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)
alert( arr.slice() ); //
当不带参数地调用时:arr.slice()
会创建一个 arr
的副本。其通常用于获取副本,以进行不影响原始数组的进一步转换。
concat(数组拼接数组/值)
arr.concat 创建一个新数组,其中包含来自于其他数组和其他项的值。
语法:
arr.concat(arg1, arg2...)
它接受任意数量的参数 —— 数组或值都可以。
返回一个包含来自于 arr
,然后是 arg1
,arg2
的元素的新数组。
如果参数 argN
是一个数组,那么其中的所有元素都会被复制。否则,将复制参数本身
例子:
let arr = [1, 2];
// create an array from: arr and [3,4]
alert( arr.concat([3, 4]) ); // 1,2,3,4
// create an array from: arr and [3,4] and [5,6]
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
// create an array from: arr and [3,4], then add values 5 and 6
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
当拼接对象时,对象会作为一个整体添加
let arr = [1, 2];
let obj = {
aaa: "something",
};
console.log( arr.concat(obj) ); // [1, 2, {…}]
即使该对象看起来像数组一样,但仍然会被作为一个整体添加
let arr = [1, 2];
let arrayLike = {
0: "something",
length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
console.log(arr.concat(arrayLike) ) // [1, 2, {…}]
但是,如果类似数组的对象具有 Symbol.isConcatSpreadable
属性,那么它就会被 concat
当作一个数组来处理:此对象中的元素将被添加:
let arr = [1, 2];
let arrayLike = {
aaa: "something",
bbb: "else",
[Symbol.isConcatSpreadable]: true,
length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else
遍历:forEach
arr.forEach 方法对数组的每个元素执行一次给定的函数
语法:
arr.forEach(function(item[, index [, array]]) {
// ... do something with item
})[, thisArg]);// thisArg为可选参数,当执行回调函数时,用作 this 的值。
-
item
是数组中正在处理的当前元素 -
index
是数组中正在处理的当前元素的索引 -
array
是正在操作的数组
例子:
对每个元素调用 alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
这段代码更详细地介绍了它们在目标数组中的位置
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
该方法没有返回值(undefined
)。
注意:如果使用箭头函数表达式来传入函数参数,
thisArg
参数会被忽略,因为箭头函数在词法上绑定了this
值。
在数组中搜索
indexOf
arr.indexOf(searchElement[, fromIndex])
searchElement
:要查找的元素
从索引fromIndex
开始查找,返回在数组中要查找的元素searchElement
的第一个索引,如果不存在,则返回-1。
- 如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1。
- 如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找 ,以此类推。
- 注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。如果抵消后的索引值仍小于0,则整个数组都将会被查询。其默认值为0.
例子:
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
lastIndexOf
arr.lastIndexOf(searchElement[, fromIndex])
从索引fromIndex
从右向左开始查找,返回在数组中要查找的元素searchElement
的最后一个索引,如果不存在,则返回-1。
- 如果索引值大于或等于数组的长度,则整个数组会被查找。
- 如果索引值为负值,数组仍然会被从后向前查找。
- 如果索引值为负值,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
- 索引值默认为数组的长度减 1(
arr.length - 1
),即整个数组都被查找。
includes
includes()
方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
arr.includes(item[, from])
从索引 from
开始搜索 item
,如果找到则返回 true
,如果没找到,则返回 false
。
如果我们想检查是否包含某个元素,并且不想知道确切的索引,那么 arr.includes
是首选。
此外,includes
的一个非常小的差别是它能正确处理NaN
,而不像 indexOf/lastIndexOf
:
const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1(应该为 0,但是严格相等 === equality 对 NaN 无效)
alert( arr.includes(NaN) );// true(这个结果是对的)
找到具有特定条件的对象
find和findindex
find()
方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
。
语法:
let result = arr.find(function(item, index, array) {
// 如果返回 true,则返回 item 并停止迭代
// 对于 falsy 则返回 undefined
});
-
item
是当前元素。 -
index
是当前元素的索引。 -
array
是数组本身。
依次对数组中的每个元素调用该函数:
如果它返回 true
,则搜索停止,并返回 item
。如果没有搜索到,则返回 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); // John
arr.findIndex
方法(与 arr.find
方法)基本上是一样的,但它返回找到元素的索引,而不是元素本身。并且在未找到任何内容时返回 -1
。
filter
如果需要匹配的有很多,我们可以使用 arr.filter(fn)。
语法与 find
大致相同,但是 filter
返回的是所有匹配元素组成的数组:
let results = arr.filter(function(item, index, array) {
// 如果 true item 被 push 到 results,迭代继续
// 如果什么都没找到,则返回空数组
});
例子:
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(返回数组)
arr.map 方法是最有用和经常使用的方法之一。
它对数组的每个元素都调用函数,并返回结果数组。
语法:
let result = arr.map(function(item, index, array) {
// 返回新值而不是当前元素
})
例子:
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6
sort(fn)
arr.sort 方法对数组进行 原位(in-place) 排序,更改元素的顺序。(原位是指在此数组内,而非生成一个新数组。)
它还返回排序后的数组,但是返回值通常会被忽略,因为修改了 arr
本身。
语法:
arr.sort([compareFunction])
compareFunction
可选
用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode
位点进行排序。
-
firstEl
第一个用于比较的元素。
-
secondEl
第二个用于比较的元素。
例子:
let arr = [ 1, 2, 15 ];
// 该方法重新排列 arr 的内容
arr.sort();
alert( arr ); // 1, 15, 2
如果指明了 compareFunction
,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:
如果
compareFunction(a, b)
小于 0 ,那么 a 会被排列到 b 之前;如果
compareFunction(a, b)
等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);如果
compareFunction(a, b)
大于 0 , b 会被排列到 a 之前。compareFunction(a, b)
必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
function compareNumeric(a, b) {
// if (a > b) return 1;
// if (a == b) return 0;
// if (a < b) return -1;
return a - b;
}
let arr = [ 1, 2, 15 ];
arr.sort(compareNumeric);
alert(arr); // 1, 2, 15
// 上面简写arr.sort( (a, b) => a - b );
reverse反转数组
arr.reverse 方法用于颠倒 arr
中元素的顺序。
例如:
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
它也会返回颠倒后的数组 arr
。
split (分割字符串为数组)
str.split(delim) 可以通过给定的分隔符 delim
将字符串分割成一个数组。
使用"逗号后跟着一个空格"作为分隔符
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字)
}
split
方法有一个可选的第二个数字参数 —— 对数组长度的限制。如果提供了,那么额外的元素会被忽略。但实际上它很少使用:
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
alert(arr); // Bilbo, Gandalf
拆分为字母
调用带有空参数
s
的split(s)
,会将字符串拆分为字母数组:let str = "test"; alert( str.split('') ); // t,e,s,t
join(拼接字符串为数组)
arr.join(glue) 与 split
相反。它会在它们之间创建一串由 glue
粘合的 arr
项。
例子:
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串
alert( str ); // Bilbo;Gandalf;Nazgul
reduce/reduceRight(根据数组计算单个值)
当我们需要遍历一个数组时 —— 我们可以使用 forEach
,for
或 for..of
。
当我们需要遍历并返回每个元素的数据时 —— 我们可以使用 map
。
arr.reduce 方法和 arr.reduceRight 方法和上面的种类差不多,但稍微复杂一点。它们用于根据数组计算单个值。
语法是:
let value = arr.reduce(function(accumulator, item, index, array) {
// ...
}, [initial]);
该函数一个接一个地应用于所有数组元素,并将其结果“搬运(carry on)”到下一个调用。
参数:
-
accumulator
—— 是上一个函数调用的结果,第一次等于initial
(如果提供了initial
的话)。 -
item
—— 当前的数组元素。 -
index
—— 当前索引。 -
arr
—— 数组本身。
应用函数时,上一个函数调用的结果将作为第一个参数传递给下一个函数。
因此,第一个参数本质上是累加器,用于存储所有先前执行的组合结果。最后,它成为 reduce
的结果。
在这里,我们通过一行代码得到一个数组的总和:
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15
传递给 reduce
的函数仅使用了 2 个参数,通常这就足够了
让我们看看细节,到底发生了什么。
- 在第一次运行时,
sum
的值为初始值initial
(reduce
的最后一个参数),等于 0,current
是第一个数组元素,等于1
。所以函数运行的结果是1
。 - 在第二次运行时,
sum = 1
,我们将第二个数组元素(2
)与其相加并返回。 - 在第三次运行中,
sum = 3
,我们继续把下一个元素与其相加,以此类推……
计算流程:
或者以表格的形式表示,每一行代表的是对下一个数组元素的函数调用:
current |
result |
sum |
|
---|---|---|---|
第 1 次调用 | 0 |
1 |
1 |
第 2 次调用 | 1 |
2 |
3 |
第 3 次调用 | 3 |
3 |
6 |
第 4 次调用 | 6 |
4 |
10 |
第 5 次调用 | 10 |
5 |
15 |
在这里,我们可以清楚地看到上一个调用的结果如何成为下一个调用的第一个参数。
我们也可以省略初始值:
let arr = [1, 2, 3, 4, 5];
// 删除 reduce 的初始值(没有 0)
let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15
结果是一样的。这是因为如果没有初始值,那么 reduce
会将数组的第一个元素作为初始值,并从第二个元素开始迭代。
计算表与上面相同,只是去掉第一行。
但是这种使用需要非常小心。如果数组为空,那么在没有初始值的情况下调用 reduce
会导致错误。
例子:
let arr = [];
// Error: Reduce of empty array with no initial value
// 如果初始值存在,则 reduce 将为空 arr 返回它(即这个初始值)。
arr.reduce((sum, current) => sum + current);
所以建议始终指定初始值。
arr.reduceRight 和 arr.reduce 方法的功能一样,只是遍历为从右到左。
Array.isArray(判断是否为数组)
数组是基于对象的,不构成单独的语言类型。
所以 typeof
不能帮助从数组中区分出普通对象:
alert(typeof {}); // object
alert(typeof []); // object
Array.isArray(value)可以对此进行判断,如果 value
是一个数组,则返回 true
;否则返回 false
。
alert(Array.isArray({})); // false
alert(Array.isArray([])); // true
thisArg(附加参数)
几乎所有调用函数的数组方法 —— 比如 find
,filter
,map
,除了 sort
是一个特例,都接受一个可选的附加参数 thisArg
。
上面的部分中没有解释该参数,因为该参数很少使用以下是这些方法的完整语法:
arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg 是可选的最后一个参数
thisArg
参数的值在 func
中变为 this
。
例如,在这里我们使用 army
对象方法作为过滤器,thisArg
用于传递上下文(passes the context):
let army = {
minAge: 18,
maxAge: 27,
canJoin(user) {
return user.age >= this.minAge && user.age < this.maxAge;
}
};
let users = [
{age: 16},
{age: 20},
{age: 23},
{age: 30}
];
// 找到 army.canJoin 返回 true 的 user
let soldiers = users.filter(army.canJoin, army);
alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23
如果在上面的示例中我们使用了 users.filter(army.canJoin)
,那么 army.canJoin
将被作为独立函数调用,并且这时 this=undefined
,从而会导致即时错误。
可以用 users.filter(user => army.canJoin(user))
替换对 users.filter(army.canJoin, army)
的调用。前者的使用频率更高,因为对于大多数人来说,它更容易理解。
总结
数组方法备忘单:
- 添加/删除元素:
-
push(...items)
—— 向尾端添加元素, -
pop()
—— 从尾端提取一个元素, -
shift()
—— 从首端提取一个元素, -
unshift(...items)
—— 向首端添加元素, -
splice(pos, deleteCount, ...items)
—— 从index
开始删除deleteCount
个元素,并在当前位置插入items
。 -
slice(start, end)
—— 创建一个新数组,将从位置start
到位置end
(但不包括end
)的元素复制进去。 -
concat(...items)
—— 返回一个新数组:复制当前数组的所有元素,并向其中添加items
。如果items
中的任意一项是一个数组,那么就取其元素。
-
- 搜索元素:
-
indexOf/lastIndexOf(item, pos)
—— 从位置pos
开始搜索item
,搜索到则返回该项的索引,否则返回-1
。 -
includes(value)
—— 如果数组有value
,则返回true
,否则返回false
。 -
find/filter(func)
—— 通过func
过滤元素,返回使func
返回true
的第一个值/所有值。 -
findIndex
和find
类似,但返回索引而不是值。
-
- 遍历元素:
-
forEach(func)
—— 对每个元素都调用func
,不返回任何内容。
-
- 转换数组:
-
map(func)
—— 根据对每个元素调用func
的结果创建一个新数组。 -
sort(func)
—— 对数组进行原位(in-place)排序,然后返回它。 -
reverse()
—— 原位(in-place)反转数组,然后返回它。 -
split/join
—— 将字符串转换为数组并返回。 -
reduce(func, initial)
—— 通过对每个元素调用func
计算数组上的单个值,并在调用之间传递中间结果。
-
- 其他:
-
Array.isArray(arr)
检查arr
是否是一个数组。
-
请注意,sort
,reverse
和 splice
方法修改的是数组本身。
这些是最常用的方法,它们覆盖 99% 的用例。但是还有其他几个:
-
arr.some(fn)/arr.every(fn) 检查数组。
与
map
类似,对数组的每个元素调用函数fn
。如果任何/所有结果为true
,则返回true
,否则返回false
。 arr.fill(value, start, end) —— 从索引
start
到end
,用重复的value
填充数组。arr.copyWithin(target, start, end) —— 将从位置
start
到end
的所有元素复制到 自身 的target
位置(覆盖现有元素)。
有关完整列表,请参阅 手册。
参考:
1.现代javascript教程
2.mdn