JS数组相关方法总结

232 阅读14分钟

开发中,数组的使用场景非常多,平时也涉及到很多数组的api相关操作,很多时候就算用过几次数组的api,在开发中也很容易忘记,还要谷歌一下,所以本篇文章便是对这块内容有一个比较系统性的总结。

image.png

创建一个数组

1. 使用Array构造函数

// 创建空数组
const arr1 = new Array()  // 

// 创建一个数组长度为3的数组
const arr2 = new Array(3)  // [,,]

// 创建一个包含三个元素的数组
const arr3 = new Array(1, 2, 3) // [1,2,3]

在使用Array构造函数时,可以省略new操作符

2. 使用数组字面量(最常用的方式)

const arr = [1, 2, 3]

ES6新增用于创建数组的两个静态方法

1. Array.of() 将一组参数转换为数组实例

定义:返回由所有参数值组成的数组,如果没有参数,就返回一个空数组

目的:解决上述使用构造函数因参数个数不同,导致行为有差异的问题

const arr1 = Array.of(3) // [3]

const arr2 = Array.of(1,2,3) // [1,2,3]

2. Array.from 将类数组结构转换为数组实例

定义:用于将两类数据结构转换为数组(不改变原对象,返回新的数组)

两类数据结构任何可迭代的结构 && 有一个length属性和可索引元素的结构

参数:

  • 第一个参数(必需):类数组对象
  • 第二个参数(可选):类似数组map方法,对每个元素进行处理,将处理后的值放入返回的数组
  • 第三个参数(可选):用于指定映射函数中的this
// 1. 可迭代对象
const arr1 = Array.form('hello'); // ['h', 'e', 'l', 'l', 'o']
const arr2 = Array.from(new Set(1,2)); // [1, 2]

// 2. 有一个length属性和可索引元素的结构
const arrayLikeObject = {
    0: 1,
    1: 2,
    2: 3,
    3: 4,
    length: 4
}
const arr = Array.from(arrayLikeObject)

function getArgsArray() {
    return Array.from(arguments)
}

常用方法

数组原型提供了非常多的方法,这里分三类进行总结,一类是会改变原数组的值,一类是不会改变原数组,以及数组的遍历方法

改变原数组的方法(9个)

ES5: splice() sort() reverse() pop() push() shift() unshift() 

ES6: fill() copyWithin()

1. splice() 删除/添加/替换数组元素

语法:arr.splice(index, howmany, item1, ..., itemX)

参数:

  • index: 必需。整数,规定添加或删除元素的位置
  • howmany: 可选。要删除的元素数量,如果设置为0,则不会删除项目
  • item1, ..., itemX:可选。像数组添加的新元素

返回值:始终返回这样一个数组,它包含从数组中被删除的元素(如果没有删除元素,则返回空数组)

删除元素

let arr = [1, 2, 3, 4, 5, 6, 7];
let removedArr = arr.splice(0, 3);
console.log(arr); // [4,5,6,7]
console.log(removredArr) // [1,2,3]

添加元素

let arr = [1, 2, 3, 4, 5, 6, 7];
let item = arr.splice(0, 0, '添加1' ,'添加2'); 
console.log(item) // [] 没有删除元素,返回空数组
console.log(arr); // ['添加1','添加2', 1, 2, 3, 4, 5, 6, 7]

替换元素

let arr = [1, 2, 3, 4, 5, 6, 7];
let item = arr.splice(1, 1, '替换我')
console.log(item) // [2]
console.log(arr) // [1, '替换我', 3, 4, 5, 6, 7]

2. sort() 数组排序

定义:对数组元素进行排序,并返回这个数组

默认sort()方法美誉传比较函数的话,默认按字母排序,如果元素不是字符串的话,会调用toString()方法将元素转化为字符的Unicode位点,然后再比较字符。

// 字符串排列 看起来很正常
var a = ["Banana", "Orange", "Apple", "Mango"];
a.sort(); // ["Apple","Banana","Mango","Orange"]


// 数字排序的时候 因为转换成Unicode字符串之后,有些数字比较大会排在后面 这显然不是我们想要的
var	a = [10, 1, 3, 20,25,8];
console.log(a.sort()) // [1,10,20,25,3,8];

比较函数的两个参数

sort的比较函数有两个默认参数,要在函数中接收这两个参数,这两个参数是数组中两个要比较的元素,通常我们用a和b接收两个将要比较的元素:

  • 若比较函数返回值<0, 那么a将排到b的前面
  • 若比较函数返回值=0,那么a和b的相对位置不变
  • 若比较函数返回值>0, 那么a将排到b的后面

sort排序常见用法:

  • 数组元素为数字的升序、降序
let arr1 = [10, 1, 3, 4, 20, 4, 25, 8]

// 升序
arr1 = arr1.sort((a, b) => a - b);
console.log(arr) // [1, 3, 4, 4, 8, 10, 20, 25]

// 降序
let arr2 = [10, 1, 3, 4, 20, 4, 25, 8]
arr2 = arr2.sort((a, b) => b - a)
console.log(arr2) // [25,20,10,8,4,4,3,1];
  • 数组多条件排序
let arr = [{id:10,age:2},{id:5,age:4},{id:6,age:10},{id:9,age:6},{id:2,age:8},{id:10,age:9}];
arr = arr.sort((a,b) => {
  // 如果id的值相等,按照age的值降序
 if(a.id === b.id){
    return b.age - a.age
 }else{ 
   // 如果id的值不相等,按照id的值升序
   return a.id - b.id
 }
})
conosle.log(arr) // [{"id":2,"age":8},{"id":5,"age":4},{"id":6,"age":10},{"id":9,"age":6},{"id":10,"age":9},{"id":10,"age":2}] 
  • 自定义比较函数
let arr = [{name:'Koro1'},{name:'Koro1'},{name:'OB'},{name:'Koro1'},{name:'OB'},{name:'OB'}];
arr = arr.sort((a,b) => {
    if(a.name === 'Koro1'){
      // 如果name是'Koro1' 返回-1 ,-1<0 a排在b的前面
      return -1
    }else{ 
      // 如果不是的话,a排在b的后面
      return 1
    }
})
console.log(arr) // [{"name":"Koro1"},{"name":"Koro1"},{"name":"Koro1"},{"name":"OB"},{"name":"OB"},{"name":"OB"}] 

3. reverse() 反转数组

定义:用于颠倒数组中元素的顺序, 并返回反转的数组

let arr = [1,2,3]
const arr2 = arr.reverse()
console.log(arr2) // [3,2,1]
console.log(arr) // [3,2,1]

4. pop() 删除数组中的最后一个元素

定义:删除一个数组中的最后一个元素,并返回这个元素

let arr = [1,2,3]
const item = arr.pop()

console.log(item) // 3
console.log(arr) // [1,2]

5. push() 向数组的末尾添加元素

定义:向数组末尾添加一个或多个元素,并返回新的长度

let arr = [1,2,3]
let count = arr.push(4,5,6)

console.log(count) // 6
console.log(arr) // [1,2,3,4,5,6]

6. shift() 删除数组中的第一个元素

定义:删除数组的第一个元素,并返回这个元素

let arr = [1,2,3];
const item = arr.shift();

console.log(item) // 1
console.log(arr) // [2, 3]

7. unshift() 在数组开头添加元素

定义:可向数组的开头添加一个或更多元素,返回新的数组长度

let arr = [1,2,3]
let count = arr.unshift('test1', 'test2') 

console.log(count) // 4
console.log(a) // ['test1', 'test2', 1, 2, 3]

8. ES6: fill() 填充数组

定义:可以向一个已有的数组中插入全部或部分相同的值。

包含三个参数:第一个参数是要填充数组的值;第二个参数是填充开始的位置(默认值为0);第三个参数是填充结束的位置(不包含结束索引)

let zeros = [0,0,0,0,0]
zeros = zeros.fill(5) // [5,5,5,5,5]
zeros = zeros.fill(0) // [0,0,0,0,0]
zeros = zeros.fill(6,3) // [0,0,0,6,6]
zeros = zeros.fill(0)  // [0,0,0,0,0]
zeros = zeros.fill(7,1,3) // [0,7,7,0,0]

9. ES6: copyWithin()

不常用,自行总结


不改变原数组的方法(8个)

1. slice() 浅拷贝数组的元素

定义: 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。

语法:arr.slice(begin , end)

参数:

  • begin: 可选,索引值,接受负值,开始索引,默认值为0
  • end: 可选, 索引值,接受负值,结束索引(不包括)
let a = ['hello', 'world']
let b = a.slice(0, 1) 
console.log(b) //  ['hello']
console.log(a) //  ['hello', 'world']

b[0] = '改变拷贝的数组'

console.log(a, b) // ['hello', 'world'], ['改变拷贝的数组']

新数组是浅拷贝的,元素是简单数据类型,改变之后不会互相干扰; 如果是复杂数据(对象,数组)的话,改变其中一个,另外一个也会改变。

let a = [{name:'doudou'}];
let b = a.slice();
console.log(b,a); // [{"name":"doudou"}]  [{"name":"doudou"}]

a[0].name='改变原数组';
console.log(b,a); // [{"name":"改变原数组"}] [{"name":"改变原数组"}]
 
b[0].name='改变拷贝数组',b[0].koro='改变拷贝数组'; //[{"name":"改变拷贝数组","koro":"改变拷贝数组"}] [{"name":"改变拷贝数组","koro":"改变拷贝数组"}]

总结:slice()是浅拷贝,对于复杂的数据类型浅拷贝,拷贝的只是指向原数组的指针,所以无论改变原数组,还是浅拷贝的数组,都是改变原数组的数据。

2. join() 数组转字符串

定义:接受一个参数,即字符串分隔符,返回包含所有项的字符串

let a = ['hello', 'world'];
let str = a.join(); // "hello,world"
let str2 = a.join('+'); // "hello+world"
let str3 = [{name:'doudou',age:'23'}, 'test'].join(); // "[object Object],test"

join()/toString()方法在数组元素是数组的时候,会将里面的数组也调用join()/toString(), 如果是对象的话,对象会被转为[object Object]字符串。

3. toLocaleString() 数组转字符串

定义: 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。

let a = [{name:'doudou'}, 23, 'abcd', new Date()];
let str = a.toLocaleString(); // [object Object], 23, abcd, 2018/5/28 下午1:52:20 

4. toString() 数组转字符串

定义:可把数组转换为由逗号链接起来的字符串。

语法: array.toString()

该方法的效果和join方法一样,都是用于数组转字符串的,但是与join方法相比没有优势,也不能自定义字符串的分隔符,因此不推荐使用。

5. concat() 合并数组

定义:用于合并两个或多个数组,返回一个新数组

语法:

const newArr = oldArr.concat(arr1, arr2, ..., arrX)
let a = [1, 2, 3];
let b = [4, 5, 6];

//连接两个数组
let newVal = a.concat(b); // [1, 2, 3, 4, 5, 6]

// 连接三个数组
let c = [7, 8, 9]
let newVal2 = a.concat(b, c); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// 添加元素
let newVal3 = a.concat('添加元素',b, c,'再加一个'); // [1, 2, 3, "添加元素", 4, 5, 6, 7, 8, 9, "再加一个"]

// 合并嵌套数组  会浅拷贝嵌套数组
let d = [1, 2];
let f = [3, [4]];
let newVal4 = d.concat(f); // [1, 2, 3, [4]]

ES6扩展运算符...合并数组

let a = [2, 3, 4, 5]
let b = [ 4, ...a, 4, 4]
console.log(a,b); //  [2, 3, 4, 5] [4,2,3,4,5,4,4]

6. indexOf() 从前往后查找数组是否存在某个元素

语法:arr.lastIndexOf(searchElement, index = 0)

参数:

  • searchElement: 必需。表示被查找的元素
  • index: 可选。表示搜索的起始位置,接受负值。
let a = ['哈哈', 2, 4, 24, NaN]
console.log(a.indexOf('哈'));  // -1 
console.log(a.indexOf('NaN'));  // -1 
console.log(a.indexOf('哈哈')); // 0

indexOf()不能识别NaN

7. lastIndexOf() 从后往前查找数组是否存在某个元素

语法:arr.lastIndexOf(searchElement, index)

参数:

  • searchElement: 必需。表示被查找的元素
  • index: 可选。逆向查找开始位置,默认值为数组的长度 - 1

关于index有三个规则:

  • 正值。如果该值大于或等于数组的长度,则整个数组会被查找。
  • 负值。将其视为从数组末尾向前的偏移。(比如-2,从数组最后第二个元素开始往前查找)
  • 负值。其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
 let a = ['doudou', 4, 'dami', 1, 2, 'dami', 3 , 4, 5, 'dami']; // 数组长度为10
 // let b = a.lastIndexOf('dami', 4); // 从下标4开始往前找 返回下标2
 // let b = a.lastIndexOf('dami', 100); //  大于或数组的长度 查找整个数组 返回9
 // let b = a.lastIndexOf('dami', -11); // -1 数组不会被查找
 let b = a.lastIndexOf('dami', -9); // 从第二个元素4往前查找,没有找到 返回-1

8. ES7:includes() 查找数组是否包含某个元素 返回布尔值

语法:arr.includes(searchElement, index = 0 )

参数:

  • searchElement: 必需。表示被查找的元素
  • index: 可选。表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false; 负值绝对值超过数组长度,重置从0开始搜索。

includes方法是为了弥补indexOf方法的缺陷而出现的:

  • indexOf方法不能识别NaN
  • indexOf方法检查是否包含某个值不够与异化,需要判断是否不等于-1,表达不够直观
let arr = ['doudou', 'dami', NaN, 'maomao']

const flag = arr.includes(NaN) / /true
const flag = arr.includes('doudou', 100) // false 超过数组长度,不搜索
const flag = arr.includes('dami', -3) // true 从倒数第三个元素开始搜索
const flag = arr.includes('doudou', -100) // true 负值绝对值超过数组长度,搜索整个数组

数组的遍历方法(12个)

js中遍历数组并不会改变原始数组的方法总共有12个:

ES5: forEach() every() some() filter() map() reduce() reduceRight()

ES6: find() findIndex() keys() values() entries()

1. forEach()

对数组每一项都运行传入的函数,没有返回值。

语法

arr.forEach(function(currentValue, index, arr){
    
}, thisValue)

参数:

  • function: 必须。数组中每个元素需要调用的函数
// 回调函数的参数
1. currentValue(必须), 数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选), 数组对象本身
  • thisValue: 可选。当执行回调函数时this绑定对象的值,默认值为undefined

2. every()

对数组每一项都运行传入的函数,如果对每一项函数都返回true, 则这个方法返回true

function isBigEnough(element, index, array) { 
  return element >= 10; // 判断数组中的所有元素是否都大于10
}
let result = [12, 5, 8, 130, 44].every(isBigEnough);   // false
let result = [12, 54, 18, 130, 44].every(isBigEnough); // true


// 接受箭头函数写法 
[12, 5, 8, 130, 44].every(x => x >= 10); // false
[12, 54, 18, 130, 44].every(x => x >= 10); // true

3. some()

对数组每一项都运行传入的函数,如果有一项函数返回true, 则这个方法返回true

function isBigEnough(element, index, array) {
  return (element >= 10); //数组中是否有一个元素大于 10
}

let result = [2, 5, 8, 1, 4].some(isBigEnough); // false
let result = [12, 5, 8, 1, 4].some(isBigEnough); // true

4. filter()

对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回。(过滤原始数组,返回新数组)

let a = [32, 33, 16, 40];

let result = a.filter(function (value, index, array) {
  return value >= 18;
});

console.log(result);// [32,33,40]
console.log(a); // [32, 33, 16, 40]

5. map()

对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。

let a = [1,2,3,4]

let res = a.map(function(currentValue, index, arr) {
    return currentValue * 2
})
console.log(res) // [1,4,6,8]
console.log(a) // [1,2,3,4]

6. reduce()

定义:对数组中的每个元素执行一个自定义的累积器,将其结果汇总为单个返回值

语法:arr.reduce((total, value, index, array) => {}, initValue)

参数:

  • callback: 回调函数(必需)
  • initValue: 初始值(可选)

回调函数的参数:

  • total: 累积器完成计算的返回值(必需)
  • value: 当前元素(必需)
  • index: 当前元素的索引(可选)
  • array: 当前元素所属的数组对象(可选)

过程:

  • 以total作为累计结果的初始值,不设置total则以数组第一个元素为初始值
  • 开始遍历,使用累积器处理value,将value的映射结果累计到total上,结束此循环,返回total
  • 进入下一次循环,重复上述操作,直至数组最后一个元素
  • 结束遍历,返回最终的total

reduce的精华所在是将累计器逐个作用于数组成员上,把上一次输出的值作为下一次输入的值。下面举个简单的栗子,看看reduce的计算结果

const arr = [3, 5, 1, 4, 2];
const a = arr.reduce((t, v) => t + v);
// 等同于
const b = arr.reduce((t, v) => t + v, 0);

代码不太明白没关系, 可参考这篇文章的动图

reduce实质上是一个累计器函数,通过用户自定义的累计器对数组成员进行自定义累计,得出一个由累计器生成的值。

对空数组调用reduce()和reduceRight()是不会执行其回调函数的,可认为reduce()对空数组无效

高级用法:

  1. 累加累乘
function getSum(...values) {
  return values.reduce((total, value) => total + value, 0);
}

function getMul(...values) {
  return values.reduce((total, value) => total * value, 1);
}

const res1 = getSum(1, 2, 3, 4)
console.log(res1)

const res2 = getMul(1, 2, 3, 4)
console.log(res2)
  1. 权重求和
const scores = [
  { score: 90, subject: "chinese", weight: 0.5 },
  { score: 95, subject: "math", weight: 0.3 },
  { score: 85, subject: "english", weight: 0.2 }
];
const res = scores.reduce((total, value) => total + value.score * value.weight, 0)
console.log(res) 
  1. 代替map和filter
const arr = [0, 1, 2, 3];

// 代替map:[0, 2, 4, 6]
const a = arr.map(v => v * 2);
const b = arr.reduce((arr, v) => {
  arr.push(v * 2);
  return arr;
}, [])

// b的简洁写法
const b1 = arr.reduce((arr, v) => [...arr, v * 2], [])
console.log(b, b1) // [ 0, 2, 4, 6 ] [ 0, 2, 4, 6 ]

// 代替filter:[2, 3]
const c = arr.filter(v => v > 1);
const d = arr.reduce((arr, value) => {
  if(value > 1) {
    arr.push(value)
  }
  return arr;
}, [])

// d的简洁写法
const d1 = arr.reduce((t, v) => v > 1 ? [...t, v] : t, []);
console.log(d, d1) // [ 2, 3 ] [ 2, 3 ]


// 代替map和filter:[4, 6]
const e = arr.map(v => v * 2).filter(v => v > 2);
const f = arr.reduce((t,v) => {
  if(v*2 > 2) {
    t.push(v*2)
  }
  return t;
}, [])

// f的简洁写法
const f1 = arr.reduce((t, v) => v * 2 > 2 ? [...t, v * 2] : t, []);
console.group(f, f1) // [ 4, 6 ] [ 4, 6 ]
  1. 代替some和every
const scores2 = [
  { score: 45, subject: "chinese" },
  { score: 90, subject: "math" },
  { score: 60, subject: "english" }
];

// 代替some: 至少一门合格
const isAtLeastOneQualified = scores2.reduce((t, v) => {
  return t || v.score >= 60
}, false)

console.log(isAtLeastOneQualified) // true

// 代替every: 全部合格
const isAllQualified = scores2.reduce((t, v) => {
  return t && v.score >= 60
}, true)

console.log(isAllQualified) // false
  1. 数组过滤
function difference(arr = [], oarr = []) {
  const res = arr.reduce((t, v) => {
    if(!oarr.includes(v)) {
      t.push(v)
    }
    return t
  }, [])
  return res
}
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [2, 3, 6]
console.log(difference(arr1, arr2)); // [1, 4, 5]
  1. 数组扁平化
function flatArr(arr = []) {
  const res = arr.reduce((t,v) => {
    return t.concat(Array.isArray(v) ? flatArr(v) : v)
  }, [])
  return res
}
const arr3 = [0, 1, [2, 3], [4, 5, [6, 7]], [8, [9, 10, [11, 12]]]];
console.log(flatArr(arr3)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  1. 数组去重
unction uniq(arr = []) {
  return arr.reduce((t, v) => {
    if(!t.includes(v)){
      t.push(v)
    }
    return t
  }, [])
}
const testArr = [2, 1, 0, 3, 2, 1, 2]
console.log(uniq(testArr)) // [2, 1, 0, 3]
  1. 数组成员统计数量
function count(arr = []) {
  return arr.reduce((t, v) => {
    if(!t[v]) {
      t[v] = 1
    } else {
      t[v] = t[v] + 1
    }
    return t
  }, {})
}
const countArr = [0, 1, 1, 2, 2, 2];

console.log(count(countArr)) // { '0': 1, '1': 2, '2': 3 }
  1. 数字千分化
function ThousandNum(num = 0) {
  const str = (+num).toString().split(".");
  const int = nums => nums.split("").reduce((t, v, i) => t + (i % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
  const dec = nums => nums.split("").reduce((t, v, i) => t + ((i + 1) % 3 ? v : `${v},`), "").replace(/^,|,$/g, "");
  return str.length > 1 ? `${int(str[0])}.${dec(str[1])}` : int(str[0]);
}
ThousandNum(1234); // "1,234"
ThousandNum(1234.00); // "1,234"
ThousandNum(0.1234); // "0.123,4"
ThousandNum(1234.5678); // "1,234.567,8"
  1. 数组转对象
const people = [
  { area: "GZ", name: "YZW", age: 27 },
  { area: "SZ", name: "TYJ", age: 25 }
];

const map = people.reduce((t, v) => {
  const { name, ...res} = v
  t[name] = res
  return t
}, {})

console.log(map) // // { YZW: {…}, TYJ: {…} }

7. reduceRight()

这个方法除了与reduce执行方向相反外,其他完全与其一致,参考上述 reduce 方法介绍。

// 代替reverse
function reverseArr(arr = []) {
  return arr.reduceRight((t, v) => {
    t.push(v);
    return t;
  } , [])
}
const res = reverseArr([1, 2, 3, 4, 5])
console.log(res) // [5,4,3,2,1]

8. ES6: find() & findeIndex()

find()定义:用于找出第一个符合条件的数组元素,并返回该成员,如果没有符合条件的元素,则返回undefined。

findIndex()定义:返回第一个符合条件的数组元素的位置,如果所有元素都不符合条件,则返回-1。

语法:

let item = arr.find(function(currentValue, index, arr), thisArg)

let index = arr.findIndex(function(currentValue, index, arr), thisArg)

这两个方法都可以识别NaN,弥补了indexOf的不足.

// find
let a = [1, 4, -5, 10].find((n) => n < 0); // 返回元素-5
let b = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n)); // 返回元素NaN

// findIndex
let a = [1, 4, -5, 10].findIndex((n) => n < 0); // 返回索引2
let b = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n));  // 返回索引4

9. ES6 keys() & values() & entries()

定义:三个方法都返回一个新的 Array Iterator对象,对象根据方法不同包含不同的值。

语法:

arr.keys()
arr.values()
arr.entries()

参考: juejin.cn/post/684490… juejin.cn/post/684490…