Javascript数组方法
数据提供了很多方法。为了简化,本节进行分组阐述。
增加/删除元素
从上节中已经知道的在数组开头或结尾的增加/删除方法。
arr.push(...items)
– 在结尾增加元素,arr.pop()
– 从结尾抽取元素,arr.shift()
– 从开头抽取元素,arr.unshift(...items)
– 在开头增加元素.
这里还有几个其他方法。
splice
如何从数组中删除一个元素?数组是对象,所以我们能尝试使用delete
.
let arr = ["I", "go", "home"];
delete arr[1]; // remove "go"
alert( arr[1] ); // undefined
// now arr = ["I", , "home"];
alert( arr.length ); // 3
元素被删除,但数组仍然有三个元素,我们可以查看arr.length == 3
。
这很自然,因为delete obj.key是根据key删除值,对对象而言没有问题。但对数组通常我们想余下的元素移动并占用空余的位置。我们希望缩短数组。
所以,应该使用特定的方法。
arr.splice(str)
方法是一把瑞士军刀,可以做任意操作:添加、删除、插入元素。语法为:
arr.splice(index[, deleteCount, elem1, ..., elemN])
index标识开始位置;deleteCount删除元素个数,后面的参数表示插入的任意元素元素。返回删除的数组。
示例可以容易掌握:
let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // from index 1 remove 1 element
alert( arr ); // ["I", "JavaScript"]
容易,对吧?从索引位置1开始,删除一个元素。
下面一个示例,我们删除3个元素,并且插入2个其他元素:
let arr = ["I", "study", "JavaScript", "right", "now"];
// remove 3 first elements and replace them by another
arr.splice(0, 3, "Let's", "dance")
alert( arr ) // now ["Let's", "dance", "right", "now"]
这里我们能看到splice返回删除的元素:
let arr = ["I", "study", "JavaScript", "right", "now"];
// remove 2 first elements
let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- array of removed elements
splice方法也能够插入元素,不删除元素。因为我们设置deleteCount为0.
let arr = ["I", "study", "JavaScript"];
// from index 2
// delete 0
// then insert "complex" and "language"
arr.splice(2, 0, "complex", "language");
alert( arr ); // "I", "study", "complex", "language", "JavaScript"
也可以使用负数作为索引值
splice和一些其他方法,索引可以为负数,特指从后往前数的位置,示例:
let arr = [1, 2, 5]
// from index -1 (one step from the end)
// delete 0 elements,
// then insert 3 and 4
arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5
slice
方法arr.slice更简单,类似arr.splice方。语法为:
arr.slice(start, end)
其返回一个新数组,拷贝从start到end所有元素(不包括end). start和end可以同时为负数,这种情况假设位置从结尾开始。
有点类型str.slice,但操作的子数组,而不是子字符串。举例:
let str = "test";
let arr = ["t", "e", "s", "t"];
alert( str.slice(1, 3) ); // es
alert( arr.slice(1, 3) ); // e,s
alert( str.slice(-2) ); // st
alert( arr.slice(-2) ); // s,t
concat
arr.concat方法连接其他数组或元素。语法:
arr.concat(arg1, arg2...)
接受任意数量参数,可以为数组或值。
结果是一个新数组,包括arr中元素,然后是arg1,arg2等。
如果一个参数是数组或有Symbol.isConcatSpreadable属性,那么其所有元素被拷贝,否则参数自身被拷贝。
举例:
let arr = [1, 2];
// merge arr with [3,4]
alert( arr.concat([3, 4])); // 1,2,3,4
// merge arr with [3,4] and [5,6]
alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6
// merge arr with [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 arrayLike = {
0: "something",
length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
//[1, 2, arrayLike]
但是如果一个类似数组对象有Symbol.isConcatSpreadable属性,那么它的元素被连接加入:
let arr = [1, 2];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else
查找数组
这些方法用于搜索数组。
indexOf/lastIndexOf/includes
indexOf,lastIndexOf,includes方法本质上和字符串语法相似,只是操作针对元素而不是字符。
- arr.indexOf(item, from) 从from开始查找item,返回找到的索引,否则返回-1.
- arr.lastIndexOf(item, from) – 相同效果,但从右向左开始查找.
- arr.includes(item, from) – 从开始位置开始查找item元素,根据查询结果返回是否.
举例:
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
注意includes方法使用===比较,所以我们查找false,正好能找到,不是0,返回true.
如果我们检查是否包括元素,并不知道其确切位置,那么includes比较合适。
find/findIndex
想像我们一个对象数组,我们如何根据特定条件查找对象,arr.find方法比较合适。
语法:
let result = arr.find(function(item, index, array) {
// should return true if the item is what we are looking for
});
参数函数对数组的每个元素进行调用:
- item 当前元素.
- index 当前索引.
- array 数组自身.
如果返回true,搜索停止,返回item。如果没有发现,返回undefined。
举例,我们有一个用户数组,每个用户有属性id和name,让查找id == 1 的用户。
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
现实世界中,对象数组很普遍,所以find方法很有用。
上面的示例中,我们仅给find单个参数 item => item.id == 1
其他参数find很少用。
arr.findIndex方法本质一样,但是返回查找到元素的索引,而不是元素自身。
filter
find方法查找单个元素(第一个匹配元素),匹配返回true,停止搜索。如果有多个匹配,我们可以使用arr.filter(fn).
语法大致和find一致,但返回匹配元素的数组。
let results = arr.filter(function(item, index, array) {
// should return true if the item passes the filter
});
举例:
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
// returns array of the first two users
let someUsers = users.filter(item => item.id < 3);
alert(someUsers.length); // 2
转换数组
本节是关于数组转换或排序的方法。
map
arr.map方法是最有用的,也是经常使用的。语法:
let result = arr.map(function(item, index, array) {
// returns the new value instead of item
}
针对数组每个元素调用参数函数,最后返回结果数组。
举例,我们转换每个元素,返回他们长度数组:
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length)
alert(lengths); // 5,7,6
sort(fn)
arr.sort方法给数组适当方式排序。
举例:
let arr = [ 1, 2, 15 ];
// the method reorders the content of arr (and returns it)
arr.sort();
alert( arr ); // 1, 15, 2
你注意到什么奇怪的结果了吗?顺序变成了 1,15,2
,不正确,可是为什么?
因为默认按照字符串方式排序。
字面上,所有元素被转换成字符串,然后比较,所以应用字典顺序,确实”2” > “15” .
为了使用我们字段排序逻辑,我们需要传递一个带两个参数的函数使用arr.sort().
函数看上去象这样:
function compare(a, b) {
if (a > b) return 1;
if (a == b) return 0;
if (a < b) return -1;
}
完整代码:
function compareNumeric(a, b) {
if (a > b) return 1;
if (a == b) return 0;
if (a < b) return -1;
}
let arr = [ 1, 2, 15 ];
arr.sort(compareNumeric);
alert(arr); // 1, 2, 15
现在,结果如我们所愿。
让我们深入看到底发生了什么。arr可以是任意类型的元素,对吧?可能包括数字、字符串、html元素等,为了排序,我们需要一个排序函数,其知道如何比较元素.缺省是按照字符串方式排序.
arr.sort(fu) 方法有一个内置排序算法实现,我们不需要关系其怎么工作(优化的快速排序算法),它在数组上运行,使用提供的函数比较其元素,给它们重新排序,我们需要做的是提供比较函数。
顺便说下,如果我们想知道那些元素被比较,没有什么阻止alert:
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
alert( a + " <> " + b );
});
算法处理过程中可能多次比较一个元素,但会尽可能少比较.
比较函数可以返回任何数
实际上,比较函数仅需要返回正数表示大于,负数表示小于。下面的写法更简短:
let arr = [ 1, 2, 15 ];
arr.sort(function(a, b) { return a - b; });
alert(arr); // 1, 2, 15
箭头函数最好
使用函数表达,可以更简洁:
arr.sort( (a, b) => a - b );
与上面效果一样。
reverse
方法arr.reverse反转数组元素顺序。
举例:
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
反转后,也返回数组。
split/join
现实生活的真实场景,我们写一个消息应用,用户写逗号分割的收件人列表:John,Pete,Mary.但对我们来说,名称数组比单个字符串更好使用,如何得到?
str.split(delim) 方法正好可以,根据分隔符打断字符串形成数组。
下面的示例:通过逗号后加上一个空格分割字符串:
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `A message to ${name}.` ); // A message to Bilbo (and other names)
}
split方法有第二个可选数字类型参数,限制数组长度。如果提供,那么额外的元素被忽略,实际中很少使用:
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
alert(arr); // Bilbo, Gandalf
Split成字母字符
调用split(s),s为‘’,将把字符串转成字符数组:
let str = "test";
alert( str.split('') ); // t,e,s,t
arr.join(str) 与 split功能相反。其从数组元创建字符串,通过str链接元素。
举例:
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';');
alert( str ); // Bilbo;Gandalf;Nazgul
reduce/reduceRight
当我们需要迭代一个数组,可以使用forEach。
当我们需要迭代数组,然后每个元素数据,可以使用map。
arr.reduce 和 arr.reduceRight方法也属于该类,但有点复杂。它们通常用于基于数组计算单个值。
语法:
let value = arr.reduce(function(previousValue, item, index, arr) {
// ...
}, initial);
参数函数应用至每个元素,你可能注意到这些熟悉的参数:
- item 当前元素.
- index 当前索引.
- array 数组自身.
所以,与forEach/map很相似,但有跟多的参数:
- previousValue是函数调用之前的结果,initial用于第一次调用。
最简单的学习是通过示例。这里给一个一行代码实现数组求和。
let arr = [1, 2, 3, 4, 5]
let result = arr.reduce((sum, current) => sum + current), 0);
alert( result ); // 15
这里我们使用reduce最常用的形式,仅使用两个参数。进一步看看细节:
- 第一次运行,sum是初始值(最后一个参数指定),等于0,然后current为第一个数组元素,为1,所以结果为1.
- 第二次运行,sum=1 ,我们给其增加数组第二元素(2),然后返回。
- 第三次运行,sum=3 ,增加后续元素,等……
计算流程图示:
用表格形式,每一行代表数组元素上的一次函数调用:
sum | current | result | |
第一次调用 | 0 | 1 | 1 |
第二次调用 | 1 | 2 | 3 |
第三次调用 | 3 | 3 | 6 |
第四次调用 | 10 | 5 | 15 |
如我们看到的,其一次调用结果变成了下一次调用的第一个参数值。
所以也能忽略初始值:
let arr = [1, 2, 3, 4, 5];
// removed initial value from reduce (no 0)
let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15
结果一样,因为没有初始化,reduce使用数组第一个元素作为初始值,然后从第二个元素开始迭代。
计算表和上面一样,只是少了第一行。
但这样我们需要格外细心,如果数组是空,那么reduct调用没有初始值会保错。所以一般建议指定初始值。
arr.reductRight功能一样,但从右向左执行。
迭代:forEach
arr.forEach方法允许在数组中每个元素上运行函数。
语法:
arr.forEach(function(item, index, array) {
// ... do something with item
});
举例,显示每个元素:
// for each element call alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
//或这样
[1,2,3,4].forEach(it=>console.log(it))
下面代码更详细显示其位置信息:
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
函数结果(无论返回什么)被丢弃和忽略。
Array.isArray
数组并不是独立的语言类型,是基于对象。所以typeof不能帮助我们区分数组和普通对象:
alert(typeof {}); // object
alert(typeof []); // same
但数组通常使用一个特定方法:arr.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 is the optional last argument
thisArg参数为func函数的上下文this。举例,这里有一个对象带有fliter方法:
let user = {
age: 18,
younger(otherUser) {
return otherUser.age < this.age;
}
};
let users = [
{age: 12},
{age: 16},
{age: 32}
];
// find all users younger than user
let youngerUsers = users.filter(user.younger, user);
alert(youngerUsers.length); // 2
上面的调用,我们使用user.younger作为过滤方法,然后提供user作为其上下文。如果不提供上下文,users.filter(user.younger)将作为独立函数调用user.younger,使用this = undefined ,那么查询将报错。
其他方法
我们已经介绍了比较常用的方法,但还有其他方法:
arr.some(fn)/arr.every(fn)
检查数组.
作用于数组中每个元素,类型与map,如果有任何一个/全部为true,则返回true,反之为false。
arr.fill(value, start, end)
从start至end,使用重复值填充数组.arr.copyWithin(target, start, end)
从start位置至end拷贝元素覆盖target位置原始.
更多方法请查看官方文档.
总结
大多数常用方法:
split/join
– 转换字符至数组或相反.splice
– 在给定位置删除或插入元素.sort
– 数组排序.indexOf/lastIndexOf
,includes
– 查找数组元素.find/filter
– 满足给定条件返回第一个或全部元素.forEach
– 对每个元素执行函数.map
– 通过函数转换数组.reduce/reduceRight
– 基于数组计算单个值.slice
– 拷贝部分数组.
这些方法实际场景中95%会使用,更完整的列表,请查看手册。
本文参考链接:https://blog.csdn.net/neweastsun/article/details/72568562