0%

JavaScript数组

在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
3
let colors = ["red", "blue", "green"];  // 创建一个包含3个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含2个元素的数组

在使用字面量表示法创建数组不会调用Array构造函数。

ES6 中 Array新增两个用于创建数组的静态方法, from()和of()。

from()用于将类数组结构转为数组实例。
of()将一组参数转换为数组实例。

form的使用场景有很多,比如:

  1. 字符串拆分数组,Array.from(string);
  2. 将Map、Set转换为数组。
  3. 对现有数组进行浅拷贝。
  4. 可以转换任何可迭代对象,或者类数组对象。
  5. 将arguments对象轻松地转换为数组。

Array.from() 还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像调用 Array.from().map() 那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函数中 this 的值。但这个重写的 this 值在箭头函数中不适用。

1
2
3
4
5
const 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
2
console.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
8
const 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. 数组求和。

    1
    2
    const arr = [1, 2, 3, 4, 5];
    const sum = arr.reduce((pre, cur) => pre + cur);
  2. 将数组转换为对象。
    一般后台返回的数据,都是数组中嵌对象,有时候我们需要按对象中某个字段如id进行查找。就会十分困难。这时我们就需要将数组转为对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const 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}
    }, {});
  3. 小数组展开成为大数组
    实现简单的数组拍平

    1
    2
    3
    4
    5
    6
    7
    const 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)
    }, [])
    }
  4. 数组展开

    1
    2
    3
    4
    5
    const arr = ["今天天气不错", "", "早上好"];

    const arr1 = arr.reduce((pre, cur) => {
    return pre.concat(cur.split(''));
    }, [])
  5. 一次遍历中进行两次计算
    通过一次遍历计算出最大值最小值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const 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)
  6. 将映射和过滤合并为一个过程
    我们希望找到没有电子邮件地址的人的用户名,返回它们用户名用逗号拼接的字符串。

    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
    const 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;
    }, "")
  7. 按顺序进行异步函数
    我们可以做的另一件事.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
    22
    function 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: [ … ]}
  8. 实现map函数

    1
    2
    3
    4
    5
    6
    Array.prototype.CustomMap = function(handler) {
    return this.reduce(function(pre, cur, index) {
    pre.push(handler.call(this, cur, index));
    return pre;
    }, []);
    }
  9. 实现filter函数

    1
    2
    3
    4
    5
    6
    7
    8
    Array.prototype.CustomFilter = function(handler) {
    return this.reduce(function(pre, cur, index) {
    if (handler.call(this, cur, index)) {
    pre.push(cur);
    }
    return pre;
    }, []);
    }
  10. 提取对象中的数据

    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
    33
    const 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
21
Array.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;
};