Skip to content

Commit

Permalink
docs: add ES6 Array document
Browse files Browse the repository at this point in the history
  • Loading branch information
feng-zhang0712 committed Dec 9, 2024
1 parent 565ee81 commit 88f55a3
Show file tree
Hide file tree
Showing 2 changed files with 354 additions and 6 deletions.
354 changes: 351 additions & 3 deletions _posts/es6/2024-09-12-array.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,354 @@ layout: post
title: ES6 - Array
categories: blog
tags:
- React
- es6
---
- ES6
- Array
---

> 本文是对[《ECMAScript 6 入门 - 数组的扩展》](https://es6.ruanyifeng.com/#docs/array) 一章中知识点的摘录,请访问原文,获取更多详细信息。
## 一、扩展运算符

### 1.1 含义

扩展运算符(`...`)将一个数组转为用逗号分隔的参数序列。该运算符主要用于函数调用。

### 1.2 替代函数的 `apply()` 方法

由于扩展运算符可以展开数组,所以不再需要 `apply()` 将数组转为函数的参数。

```javascript
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
```

### 1.3 扩展运算符的应用

#### (1)复制数组

```javascript
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
```

上面的两种写法,`a2` 都是 `a1` 的克隆(`a2` 是一个新的数组)。

#### (2)合并数组

```javascript
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
```

上面代码中,`a3``a4` 是用两种不同方法合并而成的新数组,他们都是原始数组的浅拷贝。

#### (3)与解构赋值结合

```javascript
[a, ...rest] = list
```

#### (4)字符串

扩展运算符可以将字符串转为真正的数组,并且扩展运算符能够正确识别四个字节的 Unicode 字符。因此,正确返回字符串长度的函数,可以像下面这样写

```javascript
function length(str) {
return [...str].length;
}
length('x\uD83D\uDE80y') // 3
```

凡是涉及到操作四个字节的 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。

#### (5)实现了 Iterator 接口的对象

任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

```javascript
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
```

上面代码中,`arrayLike` 是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用 `Array.from` 方法将 `arrayLike` 转为真正的数组。

#### (6)Map 和 Set 结构,Generator 函数

## 二、Array.from()

`Array.from(arrayLike, mapFn, thisArg)` 用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

- 实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的 `arguments` 对象。
- 只要是部署了 Iterator 接口的数据结构,`Array.from()` 都能将其转为数组。

扩展运算符(`...`)也可以将某些数据结构转为数组。它背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。

`Array.from` 方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有 `length` 属性。因此,任何有 `length` 属性的对象,都可以通过 `Array.from()` 或者 `Array.prototype.slice()` 转为数组,而此时扩展运算符就无法转换。

```javascript
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
```

`Array.from()` 还可以接受一个函数作为第二个参数,作用类似于数组的 `map()`,用来对每个元素进行处理,将处理后的值放入返回的数组。如果 `map()` 函数里面用到了 `this` 关键字,还可以传入 `Array.from()` 的第三个参数,用来绑定 `this`

```javascript
Array.from(arrayLike, x => x * x);
```

`Array.from()` 可以将各种值转为真正的数组,并且还提供 `map` 功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数组方法。

```javascript
Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']
```

`Array.from()` 的另一个应用是,将字符串转为数组,然后返回字符串的长度。因为它能正确处理各种 Unicode 字符,可以避免 JavaScript 将大于 `\uFFFF` 的 Unicode 字符,算作两个字符的 bug。

```javascript
function countSymbols(string) {
return Array.from(string).length;
}
```

## 三、Array.of()

`Array.of(item1, item2, ...rest)` 用于将一组值转换为数组,它总是返回参数值组成的数组。这个方法的主要目的,是弥补数组构造函数 `Array()` 的不足。因为参数个数的不同,会导致 `Array()` 的行为有差异。

```javascript
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
```

`Array.of()` 基本上可以用来替代 `Array()``new Array()`,并且不存在由于参数不同而导致的重载。它的行为非常统一。

```javascript
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
```

`Array.of()` 可以用下面的代码模拟实现。

```javascript
function ArrayOf(){
return [].slice.call(arguments);
}
```

## 四、copyWithin()

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

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

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

```javascript
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 将3号位复制到0号位
[].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]
```

## 五、find(),findIndex(),findLast(),findLastIndex()

- `find(callback, thisArg)` 用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 `true` 的成员,然后返回该成员。如果没有符合条件的成员,则返回 `undefined`
- `findIndex(callback, thisArg)` 的用法与 `find()` 非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 `-1`
- ES2022 新增了两个方法 `findLast(callback, thisArg)``findLastIndex(callback, thisArg)`,从数组的最后一个成员开始,依次向前检查,其他都保持不变。

`find()``findIndex()` 都可以发现 `NaN`,弥补了数组的 `indexOf()` 的不足。

```javascript
[NaN].indexOf(NaN) // -1
[NaN].findIndex(y => Object.is(NaN, y)) // 0
```

## 六、keys(),values() 和 entries()

ES6 提供三个新的方法—— `keys()``entries()``values()` ——用于遍历数组。它们都返回一个遍历器对象,可以用 `for...of` 循环进行遍历,唯一的区别是 `keys()` 是对键名的遍历、`values()` 是对键值的遍历,`entries()` 是对键值对的遍历。

## 七、flat(),flatMap()

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

`flat()` 默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将 `flat()` 的参数写成一个整数,表示想要拉平的层数,默认为 `1`。如果不管有多少层嵌套,都要转成一维数组,可以用 `Infinity` 关键字作为参数。

```javascript
[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]
```

`flatMap(callback, thisArg)` 对原数组的每个成员执行一个函数(相当于执行 `Array.prototype.map()`),然后对返回值组成的数组执行 `flat()`。该方法返回一个新数组,不改变原数组。`flatMap()` 还可以有第二个参数,用来绑定遍历函数里面的`this`

```javascript
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]
```

- 如果原数组有空位,`flat()` 会跳过空位。
- `flatMap()` 只能展开一层数组。

## 八、at()

长久以来,JavaScript 不支持数组的负索引,如果要引用数组的最后一个成员,不能写成 `arr[-1]`,只能使用 `arr[arr.length - 1]`

这是因为方括号运算符 `[]` 在 JavaScript 语言里面,不仅用于数组,还用于对象。对于对象来说,方括号里面就是键名,比如 `obj[1]` 引用的是键名为字符串1的键,同理 `obj[-1]` 引用的是键名为字符串 `-1` 的键。由于 JavaScript 的数组是特殊的对象,所以方括号里面的负数无法再有其他语义了,也就是说,不可能添加新语法来支持负索引。

为了解决这个问题,ES2022 为数组实例增加了 `at(index)` 方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组(TypedArray)。

```javascript
const arr = [5, 12, 8, 130, 44];
arr.at(2) // 8
arr.at(-2) // 130
```

## 九、fill()

`fill()` 使用给定值,填充一个数组。`fill()` 还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

```javascript
['a', 'b', 'c'].fill(7) // [7, 7, 7]
new Array(3).fill(7) // [7, 7, 7]
```

- 对于数组中已有的元素,会被全部抹去。
- 如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。

```javascript
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]
```

## 十、includes()

`Array.prototype.includes(searchElement, fromIndex)` 返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 `includes` 方法类似。

该方法的第二个参数表示搜索的起始位置,默认为 `0`。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 `-4`,但数组长度为 `3`),则会重置为从 `0` 开始。

没有该方法之前,我们通常使用数组的 `indexOf` 方法,检查是否包含某个值。`indexOf` 方法有两个缺点,

- 不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于 `-1`,表达起来不够直观。
- 它内部使用严格相等运算符(`===`)进行判断,这会导致对 `NaN` 的误判。`includes` 使用的是不一样的判断算法,就没有这个问题。

## 十一、toReversed(),toSorted(),toSpliced(),with()

很多数组的传统方法会改变原数组,比如 `push()``pop()``shift()``unshift()` 等等。数组只要调用了这些方法,它的值就变了。ES2023 引入了四个新方法,对数组进行操作时,不改变原数组,而返回一个原数组的拷贝。

- `Array.prototype.toReversed() -> Array`
- `Array.prototype.toSorted(compareFn) -> Array`
- `Array.prototype.toSpliced(start, deleteCount, ...items) -> Array`
- `Array.prototype.with(index, value) -> Array`

它们分别对应数组的原有方法。

- `toReversed()` 对应 `reverse()`,用来颠倒数组成员的位置。
- `toSorted()`对应 `sort()`,用来对数组成员排序。
- `toSpliced()`对应 `splice()`,用来在指定位置,删除指定数量的成员,并插入新成员。
- `with(index, value)`对应 `splice(index, 1, value)`,用来将指定位置的成员替换为新的值。

上面是这四个新方法对应的原有方法,含义和用法完全一样,唯一不同的是不会改变原数组,而是返回原数组操作后的拷贝。

## 十二、group(),groupToMap()

```javascript

```

```javascript

```

```javascript

```

## 十三、数组的空位

```javascript

```

```javascript

```

```javascript

```

## 十四、Array.prototype.sort() 的排序稳定性

```javascript

```

```javascript

```

```javascript

```

```javascript

```
6 changes: 3 additions & 3 deletions _posts/question-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,14 @@

## React [130]

- [x] [ref、createRef、useRef、forwardsRef 的区别](https://fe.ecool.fun/topic/fbe1ba95-3c26-4d09-b65d-c2dda661b280?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [ref、createRef、useRef、forwardsRef](https://fe.ecool.fun/topic/fbe1ba95-3c26-4d09-b65d-c2dda661b280?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [useState 原理](https://fe.ecool.fun/topic/b8dde1bf-f45f-4266-b6a2-6cbfabf6ec43?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [useState 使用 push、pop、splice 会引起页面渲染吗?](https://fe.ecool.fun/topic/746d796e-fd0c-44c7-b38f-00296bdfdde9?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [setState 之后发生了什么](https://fe.ecool.fun/topic/f31923c7-7157-408b-8e12-74ced305802e?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [setState 同步还是异步](https://fe.ecool.fun/topic/36be973b-0351-4a18-b6b8-5e68023e7b96?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [render、commit 阶段的执行过程](https://fe.ecool.fun/topic/7969396f-fd4f-4d33-96af-c95c0840d201?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [对 createPortal 的理解](https://fe.ecool.fun/topic/3b6458bc-88e6-4c91-a81d-0b14e5464332?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [子组件是一个 Portal,发生点击事件能冒泡到父组件吗](https://fe.ecool.fun/topic/3db534b5-5ef5-44e8-b156-54ee7f1bcc70?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [子组件是一个 Portal,发生点击能冒泡到父组件吗](https://fe.ecool.fun/topic/3db534b5-5ef5-44e8-b156-54ee7f1bcc70?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [createContext 和 useContext](https://fe.ecool.fun/topic/aca0e222-cb95-4ea8-a763-fd9a0fa65f28?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [Hooks 和 memorizedState 是什么关系?](https://fe.ecool.fun/topic/467f7aeb-e452-421e-9404-da26f158adee?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [构建组件的方式有哪些?](https://fe.ecool.fun/topic/72fbff08-b2d4-4e13-bada-3b7b01f9041a?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
Expand All @@ -389,7 +389,7 @@
- [taro 的实现原理是怎么样的?](https://fe.ecool.fun/topic/bb332cca-7fb4-4fc9-ac23-ea9100c88a50?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [单页应用提高加载速度](https://fe.ecool.fun/topic/6b8fcb09-74e7-4ced-97a0-ba7b12575684?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [判断是否是 React 元素](https://fe.ecool.fun/topic/8c396acb-2093-4216-8d4a-f8fec4e64f26?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [判断一个组件是 class component 还是 function component](https://fe.ecool.fun/topic/02cde100-0c41-4036-b224-31895cd8c339?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [判断一个组件是 class component 还是 function component](https://fe.ecool.fun/topic/02cde100-0c41-4036-b224-31895cd8c339?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [Element、Component、Node、Instance](https://fe.ecool.fun/topic/8555928b-fc6d-48dd-967f-dab7e44d2744?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [x] [React 异常捕获](https://fe.ecool.fun/topic/d27c3517-ab5a-49f5-91e9-fd973eb1cd11?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)
- [React18 新特性](https://fe.ecool.fun/topic/6f40b143-3941-44c6-ac90-9bf87795ee2c?orderBy=default&order=desc&tagId=13&exerciseCate=0&ignoreMaster=1&difficulty=)****
Expand Down

0 comments on commit 88f55a3

Please sign in to comment.