数组(Array)

... 2022-12-19 About 13 min

# 数组(Array)

# array

# 1.push(),pop() (**)

  • push往数组后面推入一个或者多个数据-------返回数组长度
  • pop往数组里面推出最后一个数据--------数组的最后一个元素
    • 以上两种方法都会改变数组长度,当两着结合使用的时候构成后进先出的原则 (两者不具有链式操作的功能,从返返回值就可以看出)
let a = []
a.push(1,2,3,4,5) // 5
a // [1,2,3,4,5]
a.pop() // 5

// 使用pop的一个小例子,根据文件名获取文件类型
let filename = 'xx.hhh.heihgei.jpg'
let type = filename.split('.').pop()
1
2
3
4
5
6
7
8
  • 有关空的相关情况
let b = []
b.push(2,4,5,)
b // [2,4,5]
[].pop() // undefined 不报错
1
2
3
4

# 2.shift(),unshift() (*)

这两位兄弟就和之前的两哥们可以理解是相反的,但是他们也是改变数组长度的

  • shift() 把数组的第一个元素推出------返回数组的第一个元素
  • unshift() 在数组的前面推入数据------返回数组的长度
// 直接说shift的一个应用,在实现new命名的时候
function _new(){
  let args = [].slice.call(arguments)
  let constructor = args.shift() // 将构造函数推出来
  let context = Object.create(constructor.prototype)
  let result = constructor.apply(constructor,args)
  return  (typeof result === 'object' && result !=null) ? result : context
}
// 使用shift()方法清空一个数组
let list = [1,2,3,4,5,6]
while(list.shift()){ // 可以看出[].shift() 返回undefined
  list.shift()
}
list // []
// 回头来讲这种方法是不可靠的,如果数组中有false或者 x == false 的x都是可以终止循环的,可以自己写两种情况试一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3.join() (*)

  • join()方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。
let a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
1
2
3
4

注意:join是数组的方法,split是字符串的方法,别搞混

  • 特殊情况的处理:如果数组成员是undefined或null或空位,会被转成空字符串。
[undefined, null].join('#') // '#'
['a',, 'b'].join('-') // 'a--b'
1
2
  • 通过call方法,这个方法也可以用于字符串或类似数组的对象。
Array.prototype.join.call('hello', '-') // "h-e-l-l-o"
let obj = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(obj, '-') // 'a-b'
// 在这个地方是不是将类数组对象转化真正的数组除了Array.prototype.slice.call(objArr)以外也可以使用join和split的结合使用
Array.prototype.join.call(obj, '-').split('-') // ?
1
2
3
4
5

# 4.concat() (**)

  • concat方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
[].concat({a: 1}, {b: 2})
// [{ a: 1 }, { b: 2 }]
[2].concat({a: 1})
// [2, {a: 1}]
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]
1
2
3
4
5
6
7
8
9
10
// 利用数组的concat()方法对数组进行降维操作 N维 ----> 1维
  function down(arr){
  return isMulti(arr) ? down(Array.prototype.concat.apply([],arr)) : arr // 这个地方返回down的时候属于尾递归
}
function isMulti (arr){
  // let result = false
  // arr.forEach(item => {
  //   if(isArray(item)){
  //     result = true
  //   }
  // })
  // return result
  return arr.some(item => isArray(item))
}
function isArray(data){
  return Object.prototype.toString.call(data).match(/\[object (.*?)\]/)[1].toLowerCase() === 'array'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 5.reverse()(*)

  • reverse方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组。
// 一个典型的例子,将字符串颠倒
function str_reverse(str){
  return str.split('').reverse().join('')
}
str_reverse('123456789') // 987654321
1
2
3
4
5

# 6.slice()(*)

slice方法用于提取目标数组的一部分,返回一个新数组,原数组不变。

arr.slice(start, end);
1
  • 它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。
let a = ['a', 'b', 'c'];
a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"]
// 参数可以为负数
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]
// 参数超出范围
a.slice(4) // []
a.slice(2, 1) // []
1
2
3
4
5
6
7
8
9
10
11
12
  • 在之前已经说到过的一个应用,将类数组转为数组
  Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
  // ['a', 'b']
  Array.prototype.slice.call(document.querySelectorAll("div"));
  Array.prototype.slice.call(arguments);  
1
2
3
4

# 7.splice() (**)

splice方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。

arr.splice(start, count, addElement1, addElement2, ...);
1
  • splice()可以做数组任意位置的增加和删除
// 删除
let a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]
1
2
3
4
// 增加
let a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
1
2
3
4
// 只传一个参数
let a = [1, 2, 3, 4];
a.splice(2) // [3, 4]
a // [1, 2] // 参数也可以为负数
1
2
3
4

# 8.sort()(*)

sort方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。所以通常我们会去改写他

[10111, 1101, 111].sort(function (a, b) {
  return a - b;
})
1
2
3
[
  { name: "张三", age: 30 },
  { name: "李四", age: 24 },
  { name: "王五", age: 28  }
].sort(function (o1, o2) {
  return o1.age - o2.age;
})
1
2
3
4
5
6
7

# 9.map() (***)

map方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。 原数组不改变,返回数组的数组长度不变

[
  {uid:'dfa',name:'xx',id:'435dasfsa434'},
  {uid:'fg',name:'yy',id:'qrafasfq345tq34'},
  {uid:'gafdg',name:'zz',id:'43qe4tasfawrq34r'},
].map(item => item.id)
1
2
3
4
5
let arr = ['a', 'b', 'c'];

[1, 2].map(function (e) {
  return this[e];
}, arr)
// ['b', 'c']
1
2
3
4
5
6

# 10.forEach() (***)

forEach方法与map方法很相似,也是对数组的所有成员依次执行参数函数。但是,forEach方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值,那么使用map方法,否则使用forEach方法。

  • 在说forEach之前,先简单的用js实现一些forEach,能够懂这个原理就自然能够回使用forEach
Array.prototype._forEach=function(a,b){
  for(let i = 0; i < this.length; i++){
    a.call(b,this[i],i,this) // 将a的上下文指向b,forEach中的三个参数分别是当前值,当前序号,数组本身
  }
}
1
2
3
4
5

# 11.filter() (***)

filter方法用于过滤数组成员,满足条件的成员组成一个新数组返回。

它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。

filter返回的新数组的长度是变化的,分清楚filter和map的区别

let obj = { MAX: 3 };
let myFilter = function (item) {
  if (item > this.MAX) return true;
};
let arr = [2, 8, 3, 4, 1, 3, 2, 9];
arr.filter(myFilter, obj) // [8, 4, 9]
1
2
3
4
5
6

# 12.some(),every()(*)

这两个方法类似“断言”(assert),返回一个布尔值,表示判断数组成员是否符合某种条件。

some方法是只要一个成员的返回值是true,则整个some方法的返回值就是true,否则返回false。

var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
  return elem >= 3;
});
// true
arr.every(function (elem, index, arr) {
  return elem >= 3;
});
// false
1
2
3
4
5
6
7
8
9

注意,对于空数组,some方法返回false,every方法返回true,回调函数都不会执行。

另外,可通过some/eveny的循环来弥补forEach中无法return的问题

# 13.reduce(),reduceRight()(*)

reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样。

这两个方法在es5的时候没有用过,做统计时候特别好用,之后的开发过程中会尝试使用,也推荐使用

// 累加
[1, 2, 3, 4, 5].reduce( (a, b) => a + b ) // 15
[1, 2, 3, 4, 5].reduce( (a, b) => a + b , 10 ) // 25 // a的初始值是10再挨个累加,最后的出的是25
// 对于空数组
[].redice( (a,b) => a+b ) // 报错
1
2
3
4
5

reduce方法和reduceRight方法的第一个参数都是一个函数。该函数接受以下四个参数。

  1. 累积变量,默认为数组的第一个成员
  2. 当前变量,默认为数组的第二个成员
  3. 当前位置(从0开始)
  4. 原数组
// 实际应用 求数组长度最大的元素
let findLongest = entries =>  entries.reduce( (longest, entry) => entry.length > longest.length ? entry : longest, '');
findLongest(['aaa', 'bb', 'c']) // "aaa"
1
2
3

# 14.indexOf(),lastIndexOf() (**)

indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1。(lastIndexOf先后顺序的问题,这个地方就只说indexOf,lastIndexOf使用同理)

let a = ['a', 'b', 'c'];
a.indexOf('a') // 0
a.indexOf('d') // -1
// 在开发过程中会很有作用
~a.indexOf('a') // 非0 ------------> !!~a.indexOf('a') // true 在数组里面存在a
~a.indexOf('d') // 0---------------> !!~a.indexOf('d') // false在数组里面不存在d
1
2
3
4
5
6

~ 会将-1--->0,其他的转化为非0,当然肯定有他自己的转化规则,这个地方不去详细说明

# 15.链式操作 (***)

上面这些数组方法之中,有不少返回的还是数组,所以可以链式使用。

let users = [
  {name: 'tom', email: 'tom@example.com'},
  {name: 'peter', email: 'peter@example.com'}
];

users
.map( user => user.email)
.filter( email = /^t/.test(email))
.forEach( email => {
  console.log(email)
})
// "tom@example.com"
1
2
3
4
5
6
7
8
9
10
11
12

数组的很多方法和es6联合起来使用看起来会简单很多,可以使用链式操作的方法主要看数组的返回值就好,如果返回的是数组则可以使用链式操作,在这儿也特别强调自己一遍一定要注意每一种方法数组的返回值,使用逆向思维去使用数组会清楚很多

# ES6扩展

# 1. 扩展运算符(...

# 含义

扩展运算符(spread)是三个点(...),这哥们就好比是rest参数的逆运算,将一个数组转为用逗号分隔的参数序列

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

该运算符主要用于函数调用

let push = (array, ...item) => {
  array.push(item)
}
let add = (x, y) => x+y
let nums = [2,3]
add(...nums) // 5
1
2
3
4
5
6

对于空数组

[...[],1] // [1]
1

注意:只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错

(...[0,1,2,3,]) // error
console.log( (...[0,1,2,3,]) ) // error
1
2

理解扩展运算符:可以理解为把数组里面的东西拿出来,放在所在的位置

# 替换函数的apply方法

由于扩展运算符可以展开数组,有些关于数组上面需要用到apply的方法就可以被取代了

// ES5
function f(x,y,z){
  // do something
}
var args = [1,2,3]
f.apply(null,args)
// ES6
f(...args)
1
2
3
4
5
6
7
8

类似的还有很多这样的处理

# 扩展运用符的应用
  1. 复制数组
// ES5
let arr1 = [1,2,3,4]
let arr2 = arr1.concat()
// ES6
let arr = [1,2,3,4,5]
let newArr = [...arr]
1
2
3
4
5
6
  1. 合并数组
let arr1 = [1,2]
let arr2 = [3,4]
let arr = [...arr1, ...arr2]
1
2
3

# 2.Array.from()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构SetMap)。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
}
// ES5
let arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

Array.from([1, 2, 3]) // [1,2,3]
1
2
3
4
5
6
7
8
9
10
11
12

值得提醒的是,扩展运算符(...)也可以将某些数据结构转为数组

// arguments对象
function foo() {
  const args = [...arguments];
}

// NodeList对象
[...document.querySelectorAll('div')]
1
2
3
4
5
6
7

扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。

Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
...{length:3}
// error
1
2
3
4

实现

const toArray = (() =>
  Array.from ? Array.from : obj => [].slice.call(obj)
)();
1
2
3

# 3.Array.of()

Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
Array.of() // []
Array.of(undefined) // [undefined]
1
2
3
4
5

实现

function ArrayOf(){
  return [].slice.call(arguments);
}
1
2
3

# 4.数组实例的copyWithin()

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

Array.prototype.copyWithin(target, start = 0, end = this.length)
1

参数解读:

  1. target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  2. start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
  3. end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。

这三个参数都应该是数值,如果不是会自动转为数值

替换的长度取决于start-end的长度

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}
// 将2号位到数组结束,复制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// 对于没有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5.数组实例的find()和findIndex()

  1. find() 和filter不一样,find是找到第一个满足条件的元素,然后返回这个元素,没有返回undefined
  2. findIndex和find只是返回值不一样,返回元素的下标,没有返回-1

# 6. Array.entries(), keys(), values() 联合for...of使用

  1. keys() 遍历键名
  2. values() 遍历键值
  3. entries() 遍历键值对
for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 7.数组实例的 flat(),flatMap()

数组的拉平,降维

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

可以接收一个参数,默认为1,表示拉平一次,Infinity 表示不管多少层,都拉成一维数组

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]
1
2
3
4
5
6
7
8

flat()会跳过空位,还有说一下注意返回值是一个新数组,没有改变老数组

flatMap()实在没有用到过,如果用到了可以看一下文档

补充说明一下,数组的完全拉平,还有其他的方法

  1. 递归

  2. toString+split

let arr = [1, 2, [3, [4, 5]]]
console.log(arr.toString().split(','))
1
2
  1. replace + JSON.parse
let arr = [1, 2, [3, [4, 5]]]
console.log(JSON.parse(`[${arr.toString()}]`))
1
2
  1. reduce方法
function flatten(ary) {
    return ary.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
    }, []);
}
let ary = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(ary))
1
2
3
4
5
6
7
  1. 扩展运算符
//只要有一个元素有数组,那么循环继续
while (ary.some(Array.isArray)) {
  ary = [].concat(...ary);
}
1
2
3
4

# 8.数组的空位

ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  1. forEach(), filter(), reduce(), every() 和some()都会跳过空位。
  2. map()会跳过空位,但会保留这个值
  3. join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
[,'a'].forEach((x,i) => console.log(i)); // 1

// filter方法
['a',,'b'].filter(x => true) // ['a','b']

// every方法
[,'a'].every(x => x==='a') // true

// reduce方法
[1,,2].reduce((x,y) => x+y) // 3

// some方法
[,'a'].some(x => x !== 'a') // false

// map方法
[,'a'].map(x => 1) // [,1]

// join方法
[,'a',undefined,null].join('#') // "#a##"

// toString方法
[,'a',undefined,null].toString() // ",a,,"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

ES6则明确

ES6数组扩展方法 (opens new window)

Last update: December 19, 2022 11:58
Contributors: salvatoreliaoxuan , liaoxuan