Skip to main content
 首页 » 编程设计

Javascript数组方法

2022年07月19日113qlqwjy

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最常用的形式,仅使用两个参数。进一步看看细节:

  1. 第一次运行,sum是初始值(最后一个参数指定),等于0,然后current为第一个数组元素,为1,所以结果为1.
  2. 第二次运行,sum=1 ,我们给其增加数组第二元素(2),然后返回。
  3. 第三次运行,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