前言
上一篇文章 介绍了与原始值类型相关的内置对象,以及 Math 和 Date 这两个常用的内置对象。本文将介绍 JavaScript 中数据结构相关的内置对象,包括 Array、Map、WeakMap、Set、WeakSet,对于 Int8Array、Uint8Array 这样的 TypedArray 只会简单介绍。
1. 数据结构
1.1 Array
Array 在 JavaScript 中算是最常用的数据结构,它跟其它编程语言里的 Array 有很大的区别。
- JavaScript 数组内的元素可以是不同的数据类型
- JavaScript 数组的
length可以直接修改 - JavaScript 数组可以通过
push/pop/shift/unshift/splice方法来直接给数组追加/删除元素
const arr = [];
arr.length = 2; // 直接将数组长度修改为 2,空位填充 `undefined`
arr.push(3); // 从数组尾部添加元素,数组长度增加 1,变为 3
arr.unshift(-1); // 从数组头部添加元素,数组长度增加 1,变为 4
arr.pop(); // 取出数组尾部的第 1 个元素,数组长度减少 1,变为 3
arr.shift(); // 取出数组头部的第 1 个元素,数组长度减少 1,变为 2
JavaScript 中还有一种数组叫做 "类型数组 (TypedArray)",与之对应的是 ArrayBuffer 和 Int8Array、Uint8Array、Int16Array、Uint16Array 等。
创建方式
数组可以通过多种方式创建:
- 字面量 (最常用)
- 使用
Array.form(source) - 使用构造函数
Array(强烈建议 不 要用这种方式)
使用与操作
JavaScript Array 提供了 forEach, map, reduce, pop, push, shift, unshift 等丰富的实例方法,以及 length 这个唯一的实例属性。此外,Array 构造函数也提供了 from, of, isArray 这三个静态方法,用于数组创建和数组判断。这些方法和属性的使用,在开发过程中多翻一翻文档就好,不用死记硬背。
1.2 Map
Map 是用于保存 key/value 映射关系的一种数据结构,它和 Object 很像,但两者还是有很大的区别,这在 MDN Map-描述 里进行了详细的对比。
Map 的常用操作:
- 使用
set(key, value)方法插入值 - 使用
get(key)方法获取值 - 使用
delete(key)方法来删除值 - 使用
clear()方法来清空整个Map实例
const map = new Map();
const objKey = {};
map.set(objKey, "这是一个对象 key 的属性");
map.set(1, "这个值的 key 是数字 1");
console.log(map.get(objKey)); // "这是一个对象 key 的属性"
使用场景:
- 需要记录非
string/symbol键的键值对映射时 - 需要一个非常 “纯净” 的键值对映射容器时
- 需要保证
key的遍历顺序时 - 需要记录键值对映射数量时
- 需要对键值对进行频繁的增删时
- 键值对映射不需要序列化时
1.3 WeakMap
WeakMap 的 key 只能是对象,且对这个对象是弱引用的 (这个对象可以在不被其他地方引用时被回收),它的值可以是任意类型的。
WeakMap 只有 get, set, delete, has 4个实例方法,使用起来很简单,当然,它的使用场景也相对较少,你可以查阅 Mdn WeakMap 来做一个更细致一点的了解。
1.4 Set
Set 相当于是一个容器,它可以存储任意类型的值,但无论是原始值还是引用值,都只会出现一次。
- 原始值只会出现一次
- 引用值只会出现相同引用
Set 中的元素不能直接访问,因此它没有 get() 方法,只有 add, clear, delete, has, entries, forEach, keys, values 方法,以及 size 属性。
keys方法是values方法的别名
const set = new Set();
set.add({}); // set.size = 1;
set.add({}); // set.size = 2;
console.log(set.has({})); // false
set.clear();
const obj = {};
set.add(obj); // set.size = 1
set.add(obj); // set.size = 1
console.log(set.has(obj)); // true
// 为什么会是上面这样的结果呢?
// 因为不同的对象字面量,是不同的引用值
Set 最常用的一个场景就是元素去重:
const arr = ["1", 1, 1, "1"];
const arr2 = Array.from(new Set(arr));
console.log(arr2); // ["1", 1]
1.5 WeakSet
WeakSet 也是一个容器,但它只能存储对象,且对这个对象是弱引用。
WeakSet 只有 add, delete, has 三个方法。
你可以通过在浏览器 devtools Console 面板上依次输入下面的代码来体验一下 WeakMap 和 Map 的差别
const set = new Set();
set.add({});
const weakset = new WeakSet();
weakset.add({});
console.log(set);
console.log(weakset);
看到了结果之后,按下快捷键 Ctrl + L 清空控制台,再在控制台输入下面的代码
console.log(set);
console.log(weakset);
你会发现 set 中有一个元素,而 weakset 是空的,没有元素。
2. 工具对象
2.1 Promise
Promise 是 JavaScript 中最早的真正的异步编程方案,Promise 对象实例用于表示一个异步操作的最终结果及其结果值。如果结果是完成,则包含完成返回的值;如果结果是失败,则包含失败返回的结果值。
一个 Promise 对象实例有三种状态:
- pendding: 初始状态,也是过程持续状态
- fulfilled:成功时的结果状态,即通过
resolve返回时的状态,此时可以通过then的第一个回调来获取到返回值 - rejected:失败时的结果状态,即通过
reject返回时的状态,此时可以通过then的第二个回调来获取到返回值,也可以通过catch来获取到返回值。
一旦 Promise 对象实例的状态到达了结果状态,无论是 fulfilled 还是 rejected,其结果状态、结果值就不会再改变。
Promise 对象实例只有 then, catch 和 finally 三个方法,且这三个方法返回值都是 Promise 对象实例。
- 利用这个特性,
Promise可以很轻松地实现链式调用。
const p = new Promise((resolve) => {
resolve("响应成功");
});
p.then((result) => {
console.log("第一个 then 收到响应:", result);
return "再次响应成功";
})
.then((result) => {
console.log("第二个 then 收到响应:", result);
throw new Error("抛出了一个错误");
})
.then((result) => {
console.log("第三个 then 收到响应:", result);
})
.catch((error) => {
console.log("捕获到错误:", error);
});
// 最终的输出结果为
// 第一个 then 收到响应: 响应成功
// 第二个 then 收到响应: 再次响应成功
// 捕获到错误: Error: 抛出了一个错误
再看和上一段代码类似的:
const p = new Promise((resolve) => {
resolve("响应成功");
});
p.then((result) => {
console.log("第一个 then 收到响应:", result);
throw new Error("在第一个 then 里抛出了一个错误");
// return "再次响应成功";
})
.then((result) => {
console.log("第二个 then 收到响应:", result);
// 这个异常不会被抛出,因为前一个 promise 抛出了一个异常,所以不会执行这个 then 回调
throw new Error("抛出了一个错误");
})
.then(
(result) => {
console.log("第三个 then 收到响应:", result);
return "第三次响应成功";
},
// 这个是用来捕获前面未被捕获的 promise 的异常
(error) => {
console.log("捕获到错误:", error);
return "错误捕获里的返回值";
}
)
.then((result) => {
console.log("第四个 then 收到响应:", result);
})
.catch((error) => {
console.log("最终捕获错误:", error);
});
// 此时最终的输出为
// 第一个 then 收到响应: 响应成功
// 捕获到错误: Error: 在第一个 then 里抛出了一个错误
// 第四个 then 收到响应: 错误捕获里的返回值
Promise 的静态方法有 all, allSettled, any, race, reject, resolve 共6个,它们的作用各有特点,这些方法的作用可以查阅文档 MDN Promise。
all,allSettled,any,race的参数都是一组Promise对象实例resolve和reject通常是用来将一个函数的返回值包装成Promise对象实例
Promise 最重要的是理解概念,知道基本的使用方式
然后是掌握除
resolve和reject之外的静态方法最后是明白
Promise对象实例的then/catch/finnaly回调函数都是产生的 微任务
2.2 Proxy/Reflect
Proxy 即 “代理”,它代理的目标是 Object,用于实现对象基本操作 (属性取值/属性赋值/可枚举性/方法调用等) 的拦截和自定义,Vue3 的响应式就是基于 Proxy 去做的。
Reflect 即 “反射”,它的作用是提供一系列静态方法来操作对象,最常用的场景就是用在 Proxy 的 handler 中,用以操作代理对象。
Proxy 和 Reflect 在日常的业务开发中用得特别少,一般在做一些底层开发的时候才会用到,因此只需要了解一下这两个概念即可。
推荐一篇文章,让你更好地理解
Reflect:JavaScript 中的 Reflect 反射
总结
本文先简单介绍了 JavaScript 里的几个关键数据结构内置对象:Array, Map, WeakMap, Set, WeakSet。这些内置对象里,最常用的是 Array,它的实例方法也是最多的,功能非常强大。Map/WeakMap 用来存储键值对,它们常用于需要记录键值对映射关系的场景。Set/WeakSet 是数据容器,它们里面不会存在相同的值 (原始值/引用值)。
然后介绍了 JavaScript 异步编程最重要的对象:Promise,并简单演示了 Promise 的使用。
最后介绍了 JavaScript 元编程里比较重要的两个对象:Proxy 和 Reflect。