在JS中数组的每个位置可以存储任意类型的数据。数组的长度也是动态的,会随着数据添加而自动增长。
数组对查询友好,通过下标随机访问时间复杂度为O(1)。
对插入和删除不友好,因为涉及到数组的移动,平均时间复杂度为O(n)
数组的创建
一是通过new Array构造函数的方式进行创建。
我们可以用它来创建指定的数组长度。const arr = new Array(10)
这就创建了一个数组长度为10的数组。在使用构造函数时,省略new操作符结果也一样。const arr = Array(10)
引用规范中的描述When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.
另一种方法是通过使用数组字面量的方法。1
2
3let colors = ["red", "blue", "green"]; // 创建一个包含3个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含2个元素的数组
在使用字面量表示法创建数组不会调用Array构造函数。
ES6 中 Array新增两个用于创建数组的静态方法, from()和of()。
from()用于将类数组结构转为数组实例。
of()将一组参数转换为数组实例。
form的使用场景有很多,比如:
- 字符串拆分数组,Array.from(string);
- 将Map、Set转换为数组。
- 对现有数组进行浅拷贝。
- 可以转换任何可迭代对象,或者类数组对象。
- 将arguments对象轻松地转换为数组。
Array.from()
还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像调用 Array.from().map()
那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函数中 this
的值。但这个重写的 this
值在箭头函数中不适用。1
2
3
4
5const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);
const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});
console.log(a2); // [1, 4, 9, 16]
console.log(a3); // [1, 4, 9, 16]
Array.of()
可以把一组参数转换为数组。这个方法用于替代在ES6之前常用的Array.prototype.slice.call(arguments)
,一种异常笨拙的将arguments对象转换为数组的写法:1
2console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(undefined)); // [undefined]
数组的填充
如果我们要创建数组,并且要给数组中每个元素都填充上值。我们一般用fill方法。1
const arr = (new Array(3)).fill(1)
我们要注意在二维数组的时候,这样初始化会有问题。举一个例子,1
2
3
4
5
6
7
8const arr =(new Array(3)).fill([]);
arr[0][0] = 1;
// 当你给他赋值之后,你会发现整列都会被改变
/*
0: [1]
1: [1]
2: [1]
*/
fill 传递一个入参时,如果这个入参的类型是引用类型,那么 fill 在填充坑位时填充的其实就是入参的引用。所以这三个数组对应了同一个引用。当你修改一个的时候其余两个都会改变。
所以最好用的方法也是最简单的方法,直接用for循环进行赋值。
数组的方法
数组的常见方法都很简单。我们可以简单地过一下
forEach 简单遍历,操作改变数组。
map 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
concat 合并两个数组,不改变,返回新
every 每一个元素都通过指定函数
some 至少有一个通过指定函数
filter 过滤 返回新
find 返回第一个符合指定函数的 元素值!
findIndex 返回第一个符合指定函数的 索引,没有返回-1
includes 方法用来判断一个数组是否包含一个指定的值,字符串区分大小写
indexOf 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
lastIndexOf 从后往前找,如果不存在 返回-1。
join 将数组拼成字符串
slice 从begin开始end结束浅拷贝,包含begin不包含end。返回新。
splice 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
reverse 数组颠倒,改变原数组
flat 数组拍平,参数是递归深度 返回新
sort 排序,如果没有指定排序函数,用Unicode位点进行排序。 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变(也可能会变)。如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。排序为就地算法不增加额外空间
reduce
唯一要详细讲解的就是这个reduce方法。
我们先看看Reducer的语法1
array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue)
方法对数组中的每个元素执行一个由 您 提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。
accumulator为上一次迭代函数返回结果,在初始的时候,如果传了initialValue它的初始值就为initialValue,否则就为数组第一个元素。所以,假如数组的长度为n,如果传入初始值,迭代次数为n;否则为n-1。
我们来看看reduce有哪些用法。
数组求和。
1
2const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((pre, cur) => pre + cur);将数组转换为对象。
一般后台返回的数据,都是数组中嵌对象,有时候我们需要按对象中某个字段如id进行查找。就会十分困难。这时我们就需要将数组转为对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const userList = [
{
id: 1,
username: 'john',
sex: 1,
email: 'john@163.com'
},
{
id: 2,
username: 'jerry',
sex: 1,
email: 'jerry@163.com'
},
{
id: 3,
username: 'nancy',
sex: 0,
email: ''
}
];
const userObject = userList.reduce((pre, cur) => {
return {...pre, [cur.id]: cur}
}, {});小数组展开成为大数组
实现简单的数组拍平1
2
3
4
5
6
7const arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
const _flatten = function (array) {
return array.reduce(function (prev, next) {
return prev.concat(Array.isArray(next) ? _flatten(next) : next)
}, [])
}数组展开
1
2
3
4
5const arr = ["今天天气不错", "", "早上好"];
const arr1 = arr.reduce((pre, cur) => {
return pre.concat(cur.split(''));
}, [])一次遍历中进行两次计算
通过一次遍历计算出最大值最小值。1
2
3
4
5
6
7
8
9
10
11
12
13const arr = [1,3,2,4,5,6,8,0];
const initMinMax = {
minValue: Number.MAX_VALUE,
maxValue: Number.MIN_VALUE
}
arr.reduce((pre, current) => {
return {
minValue: Math.min(pre.minValue, current),
maxValue: Math.max(pre.maxValue, current)
}
},initMinMax)将映射和过滤合并为一个过程
我们希望找到没有电子邮件地址的人的用户名,返回它们用户名用逗号拼接的字符串。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const userList = [
{
id: 1,
username: 'john',
sex: 1,
email: 'john@163.com'
},
{
id: 2,
username: 'jerry',
sex: 1,
email: 'jerry@163.com'
},
{
id: 3,
username: 'nancy',
sex: 0,
email: ''
}
];
userList.reduce((pre, cur) => {
if (cur.email !== '') {
pre = pre !== '' ? `${pre},${cur.username}` : cur.username;
}
return pre;
}, "")按顺序进行异步函数
我们可以做的另一件事.reduce()是按顺序运行promises(而不是并行)。如果您对API请求有速率限制,或者您需要将每个prmise的结果传递到下一个promise,reduce可以帮助到你。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function fetchMessages(username) {
return fetch(`https://example.com/api/messages/${username}`)
.then(response => response.json());
}
function getUsername(person) {
return person.username;
}
async function chainedFetchMessages(p, username) {
// In this function, p is a promise. We wait for it to finish,
// then run fetchMessages().
const obj = await p;
const data = await fetchMessages(username);
return { ...obj, [username]: data};
}
const msgObj = userList
.map(getUsername)
.reduce(chainedFetchMessages, Promise.resolve({}))
.then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}实现map函数
1
2
3
4
5
6Array.prototype.CustomMap = function(handler) {
return this.reduce(function(pre, cur, index) {
pre.push(handler.call(this, cur, index));
return pre;
}, []);
}实现filter函数
1
2
3
4
5
6
7
8Array.prototype.CustomFilter = function(handler) {
return this.reduce(function(pre, cur, index) {
if (handler.call(this, cur, index)) {
pre.push(cur);
}
return pre;
}, []);
}提取对象中的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33const obj = {
"result": {
"report": {
"contactInfo": {
"callsNumber": 0,
"contactsNumber": 0,
"emergencyContactHasOverdue": "No",
"sameLiainson": {
"isSame": "Yes",
"accounts": "aa"
}
}
}
}
};
const objectGetVal = (obj, expr) => {
if (!Object.is(Object.prototype.toString.call(obj), '[object Object]')) {
throw new Error(`${obj}不是对象`);
}
if (!Object.is(Object.prototype.toString.call(expr), '[object String]')) {
throw new Error(`${expr}必须是字符串`);
}
return expr.split('.').reduce((prev, next) => {
if (prev) {
return prev[next]
} else {
return undefined;
}
}, obj)
。
}
实现一个reduce
最后我们来自己尝试实现一个reduce。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Array.prototype.myReduce = function(fn, initVal) {
if (Object.prototype.toString.call(this) != '[object Array]') {
throw new Error('当前是数组的方法,不能使用到别的上面');
}
let total;
if (initVal != undefined) {
total = initVal;
} else {
total = this[0];
}
if (initVal === undefined) {
for (let i = 1; i < this.length; i++) {
total = fn(total, this[i], i, this);
}
} else {
for (let [index, val] of this.entries()) {
total = fn(total, val, index, this);
}
}
return total;
};