数组

# 数组

参考资料:js 数组详细操作方法及解析合集 (opens new window)JS数组奇巧淫技 (opens new window)

[TOC]

# 一、常用操作

# 1.1 创建数组

# 1.1.1 new Array()

  • 一个参数即为数组长度,多个参数即为参数值。
let arr1 = new Array();		//[]
let arr2 = new Array(3);	//[,,]
let arr3 = new Array(1,2,3);//[1,2,3]
1
2
3

# 1.1.2 Array.of()

  • 返回由所有参数值组成的数组,如果没有参数,就返回一个空数组。
  • 解决构造器因参数个数不同,导致的行为有差异的问题。
let arr1 = Array.of();		//[]
let arr2 = Array.of(3); 	//[3]
let arr3 = Array.of(1,2,3);	//[1.2.3]
1
2
3

# 1.1.3 Array.from()

  • 用于将两类对象转为真正的数组(不改变原对象,返回新的数组)。
# 1.1.3.1 类似数组的对象
let obj = {0: 'a', 1: 'b', length: 3};
let arr = Array.from(obj); 				// ['a','b',undefined]
1
2
  • ES5写法:[].slice.call(obj); // [1,2,empty]
  • 对象的属性名不能转换成索引号时,无效
Array.from({
  a: '1',
  b: '2',
  length:2
});		// [undefined,undefined]
1
2
3
4
5
  • 常见的类似数组对象(必有length属性)
  1. DOM操作返回的NodeList集合

  2. 函数内部的arguments对象

# 1.1.3.2 部署了 Iterator接口的数据结构
let arr = Array.from('hello'); // ['h','e','l','l','o']
let arr = Array.from(new Set(['a','b'])); // ['a','b']
1
2
  • 常见的部署了 Iterator 接口的数据结构
  1. NodeList对象

  2. 函数内部的arguments对象

  3. string

  4. Set结构

  5. Map结构

  6. Array

  7. TypedArray

  • 扩展运算符(...)也可以将部署了 Iterator 接口的数据结构转换为数组,但是不可以将只具有length属性的对象转换为数组。
[...document.query/selectorAll('div')]
1
# 1.1.3.3 参数
  • 第一个参数(必需):要转化为真正数组的对象。
  • 第二个参数(可选): 提供map功能。
// 不改变原数组
let s = '123';
Array.from(s, o => o + o);	// ['11', '22', '33']
console.log(s);				// '123'
1
2
3
4
  • 第三个参数(可选): map函数中this指向的对象。
    • 可以用来解耦,将处理的数据和处理方法分离。
let methods = {
    addOne: n => n + 1
}
let data = [1, 2, 3];
// 注意不要使用箭头函数,否则在定义就已经指向window
Array.from(data, function(n) {
    return this.addOne(n);
} , methods);		// [2, 3, 4]
1
2
3
4
5
6
7
8

# 1.2 不改变原数组

# 1.2.1 flat(n)

  • 会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
let arr1 = [1, 2, [3, 4]];
arr1.flat(); 	// [1, 2, 3, 4]

let arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();	// [1, 2, 3, 4, [5, 6]]

let arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);	// [1, 2, 3, 4, 5, 6]
1
2
3
4
5
6
7
8
  • 使用 Infinity 作为深度,展开任意深度的嵌套数组。
arr3.flat(Infinity); 	// [1, 2, 3, 4, 5, 6]

let arr4 = [1,2,{x:1,y:1},4,[5,6]];
console.log(arr4.flat(Infinity));	//[1,2,{x:1,y:1},4,5,6]
1
2
3
4
  • 移除数组中的空项
let arr5 = [1, 2, , 4, 5];
arr5.flat();	// [1, 2, 4, 5]
1
2
# 1.2.2.1 兼容性问题
  • 不兼容IE浏览器
// 展开一层数组
arr.reduce((acc, val) => acc.concat(val), []);
// 展开无限层绩效
function flatDeep(arr, d = 1) {
   return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
                : arr.slice();
};
flatDeep(arr1, Infinity);
1
2
3
4
5
6
7
8

# 1.2.2 concat()

用于合并两个或多个数组,返回新数组。

let array1 = ['a', 'b', 'c'];
let array2 = ['d', 'e', 'f'];

console.log(array1.concat(array2));	// ["a", "b", "c", "d", "e", "f"]
1
2
3
4

# 1.2.4 slice()

  • 浅拷贝数组,返回拷贝的数组。
  • 参数
    • begin(可选): 索引数值,接受负值,从该索引处开始提取原数组中的元素,默认值为0。
    • end(可选):索引数值(不包括),接受负值,在该索引处前结束提取原数组元素,默认值为数组末尾(包括最后一个元素)。

# 1.2.5 join()

  • 把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串。

# 1.2.6 查找

# 1.2.6.1 indexOf()
  • 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
// 数组的indexOf使用严格相等===搜索元素
let a=['xiaolin',2,4,24,NaN,'xiaolin']
console.log(a.indexOf('xiao'));  // -1 
console.log(a.indexOf('xiaolin')); // 0
1
2
3
4
  • indexOf()不能识别NaN。
console.log(a.indexOf('NaN'));  // -1 
1
# 1.2.6.2 lastIndexOf()
  • 返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。
a.lastIndexOf('xiaolin');	// 5
1
# 1.2.6.3 includes()
  • 返回一个布尔值。
  • 弥补indexOf方法的缺陷而出现的:
    1. indexOf方法不能识别NaN
    2. 更加语义化,无需判断返回值是否为-1。

# 1.3 改变原数组

# 1.3.1 fill(fillValue,start=0,end=this.length)

let array1 = [1, 2, 3, 4];

console.log(array1.fill(0, 2, 4));	// [1, 2, 0, 0]

console.log(array1.fill(5, 1));		// [1, 5, 5, 5]

console.log(array1.fill(6));		// [6, 6, 6, 6]
1
2
3
4
5
6
7

# 1.3.2 push()和unshift()

  • 返回数组的新长度。

# 1.3.3 pop()和shift()

  • 返回被删除的元素,空数组则返回undefined
  • 不接受传参,传了也没用。

# 1.3.4 splice()

  • 向/从数组中添加/删除项目,返回被删除的项目,添加时返回[]。
  • 参数:
    1. index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
    2. deleteNum:可选。要删除的项目数量,默认到结尾。如果设置为 0,则不会删除项目。
    3. addItem1, ..., addItemX: 可选。向数组添加的新项目。
let arr = [1,2,3,4];
let res = arr.splice(1);	// [2,3,4]
1
2

# 1.3.5 sort()

  • 对数组元素默认默认按照Unicode编码,从小到大进行排序。

  • 字符串排列时,直接sort()就是升序。

let a = ["Banana", "Orange", "Apple", "Mango"];
a.sort(); // ["Apple","Banana","Mango","Orange"]
1
2
  • 数字排序的时候,会先转换成Unicode字符串排列。
let	a = [10, 1, 3, 20,25,8];
a.sort(); // [1,10,20,25,3,8];
1
2
  • sort的比较函数有两个默认参数,通常我们用 a 和 b 接收两个将要比较的元素:
    • 若比较函数返回值<0,那么a将排到b的前面;
    • 若比较函数返回值=0,那么a 和 b 相对位置不变;
    • 若比较函数返回值>0,那么b 排在a 将的前面。
let array =  [10, 1, 3, 4,20,4,25,8];

// 升序
array.sort(function(a,b){
    return a-b;
});						// [1,3,4,4,8,10,20,25];

// 降序
array.sort(function(a,b){
    return b-a;
});						// [25,20,10,8,4,4,3,1];
1
2
3
4
5
6
7
8
9
10
11

# 1.3.6 reverse()

  • 颠倒数组中元素的顺序,返回颠倒后的数组。

# 1.3.7 copyWithin(targetIndex, start = 0, end = this.length)

  • 指定位置的成员复制到其他位置,并返回数组。
  • 从targetIndex位置开始替换数据,从start位置开始读取数据,到end位置前停止读取数据。
// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)	// [4, 2, 3, 4, 5]
1
2

# 1.3.8 性能问题

  • shift和unshift是对于数组的首元素进行操作的,由于数组是个引用类型的数据类型,它在变量赋予的时候其实是赋予了一个指针,也就是说我们首元素出列了或者有元素入列了,我们变量名所挂钩的指针是不会变的,所以说每次对首元素操作后面的元素都会受到影响,都会有一步操作。

    而 pop 和 push 这样的操作,每次只会对尾元素进行操作,不会对整个数组的元素有影响。

    在 Chrome 中,push方法的实现是用的汇编,而pop、unshift以及shift则是是用的 C++,这里就单独拎出unshift的来看看:

    • 先判断容量是否足够。
    • 如果不够,将容量扩展,然后把老元素移到新的内存空间偏移为unshift元素个数的位置,也就是说要腾出起始的空间放unshfit传进来的元素。
    • 如果空间足够了,则直接执行memmove移动内存空间,最后再把unshif传进来的参数copy到开始的位置。
    • 最后更新 array length。

    作者:uni-5 链接:https://leetcode-cn.com/problems/implement-stack-using-queues/solution/javascriptjie-fa-yi-ji-dui-javascriptbu-fen-apixin/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 用reverse()+push()代替unshift()

var arr1 = Array(10000).fill(1)
var s = +new Date()
for(let i = 0; i < 10000; i++) {
    arr1.unshift(i)
}
console.log(+new Date - s)	// 23

var arr2 = Array(10000).fill(1)
var s = +new Date()
arr2.reverse()
for(let i = 0; i < 10000; i++) {
    arr2.push(i)
}
arr2.reverse()
console.log(+new Date - s)	// 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 1.4 遍历数组

  • 不改变原数组。

  • ES5: forEach、every 、some、 filter、map、reduce、reduceRight。

  • ES6:find、findIndex、keys、values、entries。

  • 遍历规则

    1. 对于空数组是不会执行回调函数的。
    2. 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数。
    3. 遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历
    4. 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。
  • 注意事项

    1. 尽量不要在遍历的时候修改数组的长度(删除/添加)。
    2. 尽量不要在遍历的时候,修改后面要遍历的值。

# 1.4.1 ES5遍历

# 1.4.1.1 forEach()
  • 按升序为数组中含有效值的每一项执行一次回调函数。
  • array.forEach(function(currentValue, index, arr), ?thisValue)
let array = [1, 2, 3];
array.forEach((n, index) => n - index);	// undefined
let arr = [];
array.forEach((n, index) => arr[index] = n - index);
console.log(arr);						// [1, 1, 1]
1
2
3
4
5
  • 注意事项
    • 无法中途退出循环,只能用return退出本次回调,进行下一次回调。
    • 总是返回 undefined值,即使你return了一个值。
# 1.4.1.2 every()
  • 检测数组中所有元素是否都符合判断条件。
  • array.every(function(currentValue, index, arr), ?thisValue)
  • 遍历规则
    • 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
    • 如果所有元素都满足条件,则返回 true
let arr = [1, 2, 3, 4];
arr.every(n => {
    console.log(n);
    return n<3;
})
// 1 2 3 false
1
2
3
4
5
6
# 1.4.1.3 some()
  • 检测数组中是否有满足判断条件的元素。
  • array.some(function(currentValue, index, arr), ?thisValue)
  • 遍历规则
    • 如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。
    • 如果没有满足条件的元素,则返回false
let arr = [1, 2, 3, 4];
arr.some(n => {
    console.log(n);
    return n<3;
})
// 1 true
1
2
3
4
5
6
# 1.4.1.4 filter()
  • 返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。
  • array.filter(function(currentValue, index, arr), ?thisValue)
let arr = [1, 2, 3, 4];
arr.filter(n => {
    return n<3;
});				// [1, 2]
1
2
3
4
# 1.4.1.5 map()
  • 对数组中的每个元素进行处理,返回新的数组。
  • array.map(function(currentValue, index, arr), ?thisValue)
let arr = [1, 2, 3, 4];
arr.map(n => n * n);		// [1, 4, 9, 16]
1
2
# 1.4.1.6 reduce()
  • 对数组中的每个元素执行一个提供的reducer函数,将其结果汇总为单个返回值。
  • array.reduce(function(accumulator, currentValue, currentIndex, arr), initialValue)
  • 这里的array不能是空数组
let arr = [1, 2, 3, 4];
let reducer = (accumulator, currentValue) => accumulator + currentValue;
console.log(arr.reduce(reducer)); // 10
console.log(arr.reduce(reducer, 5));	// 15
1
2
3
4
  • 获取数组前后值
let arr = [1,2,3,4]
let reducer = (a,b) => {
    console.log(a,b)
    return b
}
a.reduce(reducer)
// 1 2 // 2 3 // 3 4
a.reduce(reducer,99)
// 99 1 // 1 2 // 2 3 // 3 4
1
2
3
4
5
6
7
8
9
# 1.4.1.7 reduceRight()
  • 除了与reduce执行方向相反外,其余完全一致。

# 1.4.2 ES6遍历

# 1.4.2.1 find()和findIndex()
  • find():用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。

    • array.find(function(currentValue, index, arr), ?thisArg)
  • findIndex():返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

    • arr.findIndex(function(currentValue, index, arr), ?thisArg)
  • 两个方法都可以识别NaN,弥补了indexOf的不足。

let arr = [1, 2, NaN, NaN];
arr.find(n => Object.is(NaN, n));		// NaN
arr.findIndex(n => Object.is(NaN, n));	// 2
1
2
3
# 1.4.2.2 keys()、values()和entries()
  • 遍历键名、遍历键值、遍历键值对,返回遍历器对象。
  • 无参数。
  • 返回结果可用for...of循环遍历。
let arr = [1, 2, 3, 4];
arr.keys();	// Array Iterator {}
JSON.stringify(arr.key());	// '{}'
for(let key of arr.keys()){
    console.log(key);		// 0 1 2 3
}

for(let value of arr.values()){
    console.log(value);		// 1 2 3 4
}

for(let keyValue of arr.entries()){
    console.log(keyValue);	
}
// [0, 1] [1, 2] [2, 3] [3, 4]	

// 解构赋值
for(let [index, value] of arr.entries()){
    console.log(index, value);		
}
// 0 1
// 1 2
// 2 3
// 3 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 1.5 检测数组

# 1.5.1 Array.isArray()

Array.isArray([]);		// true
1
  • Array.prototype 也是一个数组。
Array.isArray(Array.prototype);		// true
1
  • 当检测Array实例时, Array.isArray 优于 instanceof,因为Array.isArray能检测iframes