# Array 操作篇

## 一. Array 操作篇

本文将介绍 JavaScript 里 Array 的相关操作，内容包括：

1. 构造器：`Array()`
2. 静态方法
   * `Array.from()`, `Array.of()`
   * `Array.isArray()`
3. 实例属性 `Array.prototype.length`
4. 实例方法

### 1. 构造器

> constructor, 构造器/构造函数

`Array()` 构造器用于创建 Array 对象。

```js
let nums1 = [1, 2, 3, 4];  // 数组字面量
let nums2 = new Array(1, 2, 3, 4); // 用数组元素初始化
let nums3 = new Array(4);          // 用数组长度初始化, [0, 2^32 - 1]

console.log(nums1.length, nums1[0], nums1[3]); // 4 1 4
console.log(nums2.length, nums2[0], nums2[3]); // 4 1 4
console.log(nums3.length, nums3[0], nums3[3]); // 4 undefined undefined

// 字符串类型
let fruits1 = ['apple', 'banana', 'strawberry'];
let fruits2 = new Array('apple', 'banana', 'strawberry');
console.log(fruits1.length, fruits2.length); // 3 3
```

### 2. 静态方法

* `Array.from()` 创建一个新的 Array 实例（浅拷贝），从类数组对象或可迭代对象
* `Array.of()` 创建一个新的 Array 实例，从参数列表-数组元素
* `Array.isArray()` 判断是否为数组，返回布尔值
  * 用它来判断 TypedArray 实例，会返回 false

#### *`Array.from()`*

`Array.from()` 方法从类数组对象或可迭代对象创建一个新的 Array 实例。它有三个参数：

* 第一个参数：类数组对象/可迭代对象
  * 类数组对象：有 `length` 属性和索引元素的对象
  * 可迭代对象：实现了 `@@iterator` 方法的对象。内置的可迭代对象有 `String`, `Array`, `TypedArray`, `Map`, `Set`
* 第二个参数：在数组的每个元素上调用的映射函数（可选）
* 第三个参数：当执行映射函数时的 `this` 值（可选）

`Array.from(obj, mapFn, thisArg)` 和 `Array.from(obj).map(mapFn, thisArg)` 的结果是一样的，只是前者不创建中间数组，且映射函数只接收两个参数 `(element, index)`。

> “不创建中间数组”对于某类数组尤其重要，比如类型数组，因为中间数组的值必然会被截断以适应合适的类型。

```js
//========= Array-like Objects
Array.from({ length: 5 }, (item, index) => index); // [0, 1, 2, 3, 4]
(function () { return Array.from(arguments) })(11, 22, 33); // [11, 22, 33]
// nodelist
const images = document.getElementsByTagName('img');
const sources = Array.from(images, image => image.src);
const insecureSources = sources.filter(link => link.startsWith('http://'));

//========= Iterable Objects
// Array
Array.from([1, 2, 3]); // [1, 2, 3]
Array.from([1, 2, 3], x => x + x);  // [2, 4, 6]
// String
Array.from('hello');   // ['h', 'e', 'l', 'l', 'o']
// Set
const fruits = new Set(['apple', 'banana', 'strawberry', 'apple']);
fruits;             // {'apple', 'banana', 'strawberry'}
Array.from(fruits); // ['apple', 'banana', 'strawberry']
// Map
const person = new Map([['name', 'Jane'], ['age', 18], ['sex', 'female']]);
Array.from(person); // [['name', 'Jane'], ['age', 18], ['sex', 'female']]
Array.from(person.keys()); // ['name', 'age', 'sex']
Array.from(person.values()); // ['Jane', 18, 'female']
```

#### *`Array.of()`*

`Array.of()` 方法从可变数量的参数列表创建一个新的 Array 实例，不限参数的数量和类型。

`Array.of()` 和 `Array()` 构造器之间的区别在于对一个整数参数的处理，比如 `Array.of(3)` 会创建一个只有一个元素 3 的数组，而 `Array(3)` 会创建一个 `length` 属性是 3 的空数组（这意味着一个包含 3 个空槽（slots）的数组，而不是具有实际 `undefined` 值的槽）。

```js
// 不同点：单个整数时
Array.of(3); // [3]
new Array(3); // [empty × 3], array of 3 empty slots

// 多个时表现一致
new Array(1, 2, 3); // [1, 2, 3]
Array.of(1, 2, 3); // [1, 2, 3]
```

### 3. 实例属性

Array 对象的 `length` 属性设置或返回该数组中元素的数量，它是一个无符号的 32 位整数。

```js
new Array(2 ** 32); // Uncaught RangeError: Invalid array length
new Array(2 ** 32 - 1); // OK
```

我们可以随时设置 `length` 属性以对数组进行扩容或缩容。

```js
const nums = [1, 2, 3];
console.log(nums); // [1, 2, 3]

// 扩容
nums.length = 10;
console.log(nums); // [1, 2, 3, empty × 7],  empty slots

// 缩容
nums.length = 1;
console.log(nums); // [1]
```

`Array.prototype.length` 的属性描述符是：

| 属性描述符        | 值         |
| ------------ | --------- |
| value        |           |
| Writable     | true ✔️ ️ |
| Enumerable   | false     |
| Configurable | false     |

### 4. 实例方法

数组的实例方法 `Array.prototype.xxx()` 比较多，大约 34 个。这里对它们进行下分类，以便更好地理解和记忆。

#### *可变函数*

> mutation operations, 变异/可变操作, 即会改变原数组的

| 方法                                                    | 说明                                                                     |
| ----------------------------------------------------- | ---------------------------------------------------------------------- |
| `fill()`                                              | 值填充                                                                    |
| <p><code>push()</code><br><code>pop()</code></p>      | <p>push\_back 在尾部添加一个/多个元素，返回新 length<br>pop\_back 删除最后一个元素，返回该元素</p>  |
| <p><code>unshift()</code><br><code>shift()</code></p> | <p>push\_front 在头部添加一个/多个元素，返回新 length<br>pop\_front 删除第一个元素，返回该元素</p> |
| <p><code>sort()</code><br><code>reverse()</code></p>  | <p>原地排序<br>原地反转数组元素</p>                                                |
| `splice()`                                            | 增/删/改                                                                  |

完整参数（包括可选参数）：

* `fill(value, start, end)`
* `push(element0, element1, ..., elementN)`
* `unshift(element0, element1, ..., elementN)`
* `sort((a,b) => {})`
  * 默认是升序，按字符串排
  * 将元素转换为字符串，然后比较它们的 UTF-16 code units 序列
* `splice(start, deleteCount, item1, item2, itemN)`
  * 会更改数组的内容
  * 可以删除或替换现有元素、可以添加新元素（增删改）

eg1. `splice()`

```js
const nums = [1, 2, 3, 4];
// 删除
nums.splice(2, 1);
console.log(nums); // [1, 2, 4]
// 替换
nums.splice(2, 1, 44);
console.log(nums); // [1, 2, 44]
// 新添加
nums.splice(2, 0, 66, 666, 6666, 66666);
console.log(nums); //  [1, 2, 66, 666, 6666, 66666, 44]
// 操作多个
nums.splice(3);
console.log(nums); // [1, 2, 66]
```

eg2. `sort()`

```js
// 默认按字符串排序
let names = ['Jobs', 'Bob', 'Ann', 'Charl', 'David'];
names.sort(); // ['Ann', 'Bob', 'Charl', 'David', 'Jobs']
// 数字也会按字符串排（不符合预期）
let nums = [22, 0, -20, 7, -11, -9, 0, 1];
nums.sort(); // [-11, -20, -9, 0, 0, 1, 22, 7]
// 按数字排
nums.sort((a, b) => a > b ? 1 : -1); // -20, -11, -9, 0, 0, 1, 7, 22
```

#### *索引/查找*

| 方法                                                          | 说明                                              |
| ----------------------------------------------------------- | ----------------------------------------------- |
| `at()`                                                      | 传下标索引，支持负数                                      |
| `includes()`                                                | 传元素，返回 boolean                                  |
| <p><code>indexOf()</code><br><code>lastIndexOf()</code></p> | <p>传元素，返回第一个（最小）下标索引<br>传元素，返回倒数第一个（最大）下标索引</p> |
| <p><code>find()</code><br><code>findIndex()</code></p>      | <p>传函数，返回满足条件的第一个元素<br>传函数，返回满足条件的第一个下标索引</p>   |

完整参数（包括可选的）：

1. 传元素：检查每个元素是否和值相等
   * `includes(searchElement, fromIndex)`
   * `indexOf(searchElement, fromIndex)`
   * `lastIndexOf(searchElement, fromIndex)`
2. 传函数：使用测试函数判断
   * 箭头函数 `find((element, index, array) => { ... })`
   * 回调函数 `find(callbackFn, thisArg)`
   * 内联函数 `find(function(element, index, array) { ... }, thisArg)`

```js
let nums = [1, 2, 4, 8, 16, 32];
nums.at(-1); // 32
nums[nums.length - 1]; // 32

nums.find(x => x > 5); // 8
nums.findIndex(x => x > 5); // 3

nums.find((x, i, arr) => { console.log(x, i, arr); return x > 5; }); // 8
// 1 0 [1, 2, 4, 8, 16, 32]
// 2 1 [1, 2, 4, 8, 16, 32]
// 4 2 [1, 2, 4, 8, 16, 32]
// 8 3 [1, 2, 4, 8, 16, 32]
```

#### *判定*

| 方法        | 说明                        |
| --------- | ------------------------- |
| `some()`  | 传测试函数，若至少有一个元素满足，则返回 true |
| `every()` | 传测试函数，若每个元素都满足，则返回 true   |

```js
let nums = [1, 2, 4, 8, 16, 32];
nums.some(x => x > 5); // true
nums.every(x => x % 2 === 0); // false
```

#### *新数组*

| 方法          | 说明                   |
| ----------- | -------------------- |
| `entries()` | key/value pairs 二维数组 |
| `keys()`    | values 一维数组          |
| `values()`  | keys 一维数组            |

#### *新的数组迭代器*

| 方法                                                   | 说明                                                             |
| ---------------------------------------------------- | -------------------------------------------------------------- |
| `map()`                                              | 传函数，对数组的每个元素用回调，用返回值组成一个新数组                                    |
| `filter()`                                           | 传函数，用过滤函数返回 true 的元素们组成一个新数组                                   |
| `slice()`                                            | <p>传下标，截取数组（返回浅拷贝）<br>参数 (start, end) 对应下标范围 \[start, end)</p> |
| <p><code>concat()</code><br></p>                     | <p>合并两个/多个数组<br>参数 (value0, value1, ... , valueN)</p>          |
| <p><code>flat()</code><br><code>flatMap()</code></p> |                                                                |

```js
let nums = [1, 2, 4, 8, 16, 32];
nums.map(x => x > 5);  // [false, false, false, false, false, true, true]
nums.map(x => x + x);  // [2, 4, 6, 8, 10, 12, 14]
nums.filter(x => x > 5); // [8, 16, 32]
```

#### *其它*

| 分类  | 方法/属性                                                           | 说明                                                   |
| --- | --------------------------------------------------------------- | ---------------------------------------------------- |
| 迭代  | `forEach()`                                                     | 传函数，为每个数组元素执行一次提供的函数                                 |
| 字符串 | `join()`                                                        | 将数组变成字符串，默认用字符","                                    |
|     | <p><code>toString()</code><br><code>toLocaleString()</code></p> | <p>字符串<br>localized 字符串</p>                          |
| 其它  | <p><code>groupBy()</code><br><code>groupByToMap()</code></p>    | <p>传函数，根据返回值将元素分组到一个对象里<br>传函数，根据返回值将元素分组到 Map 里</p> |
|     | <p><code>reduce()</code><br><code>reduceRight()</code></p>      | <p>传 reducer 函数，从左到右<br>传 reducer 函数，从右到左</p>        |
|     | `copyWithin()`                                                  | 复制一系列数组元素                                            |

### 总结

本文简要介绍了 JavaScript 里 Array 的基本操作，旨在对其有个整体了解和感性认识。

|      | 操作                                                                                                                                                                                                              | 说明              |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| 构造器  | `Array()`                                                                                                                                                                                                       | 创建 Array 对象     |
| 静态方法 | <p><code>Array.from()</code><br><code>Array.of()</code></p>                                                                                                                                                     | 创建一个新的 Array 实例 |
|      | `Array.isArray()`                                                                                                                                                                                               |                 |
| 实例属性 | `Array.prototype.length`                                                                                                                                                                                        | 可变长数组           |
| 实例方法 | <p><code>fill()</code><br><code>push()</code>, <code>pop()</code> 可模拟栈<br><code>unshift()</code>, <code>shift()</code> 可模拟队列<br><code>sort()</code>, <code>reverse()</code> 原地<br><code>splice()</code> 增删改</p> | 可变操作            |
|      | <p><code>at()</code><br><code>includes()</code><br><code>indexOf()</code>, <code>lastIndexOf()</code><br><code>find()</code>, <code>findIndex()</code></p>                                                      | 索引/查找           |
|      | <p><code>some()</code><br><code>every()</code></p>                                                                                                                                                              | 判定              |
|      | <p><code>entries()</code><br><code>keys()</code><br><code>values()</code></p>                                                                                                                                   | 返回新数组           |
|      | <p><code>map()</code>, <code>filter()</code><br><code>slice()</code>, <code>concat()</code><br><code>flat()</code>, <code>flatMap()</code></p>                                                                  | 返回新的数组迭代器       |
|      | `forEach()`                                                                                                                                                                                                     | 迭代              |
|      | <p><code>join()</code><br><code>toString()</code><br><code>toLocaleString()</code></p>                                                                                                                          | 字符串             |
|      | <p><code>groupBy()</code>, <code>groupByToMap()</code><br><code>reduce()</code>, <code>reduceRight()</code><br><code>copyWithin()</code></p>                                                                    | 其它              |

各个方法的更多详情，可查阅其接口文档。

### 主要参考

* <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global\\_Objects/Array>
