前端常用的数据结构
常用的数据结构有数组、链表、栈、队列、哈希表、树、堆、图等。但是在前端的业务场景下,我们不会用到所有的数据结构,本篇博客主要讲述前端业务场景中用到的数据结构和处理方法
前端业务场景下,最常用的数据结构是数组(Array
)和对象(Object
),其次是集合(Set
) 和 Map
Array 数组
在前端业务场景中,使用频率最高的就是数组(Array
)和对象 (Object
)
JavaScript 中的数组是复杂数据类型(引用数据类型),其数组大小可变,数组的每一项可以是任意类型的值。推荐使用数组字面量声明数组
js
const nums = [1,2,3,4]
形如 [ ]
就是数组字面量,而 1, 2,3,4 就是数组内的元素,也叫数组的项
数组的第一个元素在索引 0 处,第二个在索引 1 处,以此类推,最后一个元素是数组的属性减去 1 的值,所以必须使用非负整数(或它们的字符串形式)访问数组元素
如果访问数组第一项元素,可以使用下面的形式
js
const nums = [1,2,3,4]
nums[0]
// -> 1
如果访问数组最后一项,可以使用 length - 1
或者 at()
方法
js
nums[nums.length - 1]
// -> 4
// or
nums.at(-1)
// -> 4
除此之外,如果数组的索引是变量,则采用下面的形式访问
js
const idx = 2
nums[idx]
// -> 3
在实际的项目中,很多 HTTP 请求的返回结果的字段值是简单数组或对象数组。对这些数组进行处理就是重中之重。JavaScript 提供了很多数组方法,下面从增删改查的角度详细说明重要的数组方法
- 增
给数组添加新的元素,包括不限于 concat
、push
和 unshift
等等
js
nums.push(5)
// nums.concat(5)
// -> [1,2,3,4,5]
- 删
删除数组的中的元素,主要有 shift
、toSpliced
等方法。下面重点说一下 toSpliced
方法
js
const messages = [
{ id: 1, content: "message1", type: "send" },
{ id: 2, content: "message2", type: "send" },
{ id: 3, content: "message3", type: "received" },
{ id: 4, content: "message4", type: "send" },
{ id: 5, content: "message5", type: "received" },
];
const matchIdx = messages.findIndex((m) => m.id === 3);
messages.toSpliced(matchIdx, 1);
/*
[
{ id: 1, content: 'message1', type: 'send' },
{ id: 2, content: 'message2', type: 'send' },
{ id: 4, content: 'message4', type: 'send' },
{ id: 5, content: 'message5', type: 'received' }
]
*/
- 查
查找数组中的元素,主要有 find
、findIndex
、findLastIndex
和 findLast
等等方法
常常使用 findIndex
先找到匹配元素的索引
js
const matchIndex = nums.findIndex(num => num ===2)
// -> 1
或者使用 find
查找数组的某一项元素
js
const matchItem = nums.find(num => num.value === 2)
// -> 2
- 改
通过 find
和 findIndex
等方法查找到某个元素后,根据业务需求改变该元素。此方法主要用于对象数组
比如将 messages
的第三项的 content
字段值变为the third message
,实现如下
js
const matchItem = messages.find(m => m.id === 3)
matchItem.content= 'the third message'
// ->
/*
[
{ id: 1, content: 'message1', type: 'send' },
{ id: 2, content: 'message2', type: 'send' },
{ id: 3, content: 'the third message', type: 'received' },
{ id: 4, content: 'message4', type: 'send' },
{ id: 5, content: 'message5', type: 'received' }
]
*/
除了增删改查类型的数组方法外,还有两个非常重要的数组方法,分别是 map
和 reduce
map()
方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。我们经常在 React 中用于渲染列表
jsx
const UserList = () => {
const [users, setUsers] = useState(/*...*/);
return (
<div>
<ul>
{users.map((user) => (
<li key={user}>{user}</li>
))}
</ul>
</div>
);
};
因为数组(Array)是前端业务上非常重要的数据结构,使用频率非常高、业务场景多,数组自带的原生方法有限,所以在项目上推荐使用 Lodash 第三方库处理数据,主要有以下重要的方法 cloneDeep
和 isEqual
Object 字典
除数组外,字典(Object)是另一个使用频率高的数据结构
Object
是 JavaScript 的一种数据类型。它用于存储各种键值集合和更复杂的实体,所以对象十分灵活。Object
的形式如下:
js
{
key1: value1,
key2: value2,
// ......
}
因为对象的值还可以是对象,所以字典十分灵活,能够满足绝大部分业务场景需要的数据结构。对象是前端使用最高的数据结构。
JavaScript 中的 Object 类型有许多常用方法,以下是其中一些:
- Object.keys(obj): 返回一个包含对象的所有键的数组。
- Object.values(obj): 返回一个包含对象的所有值的数组。
- Object.entries(obj): 返回一个包含对象的所有键值对的数组,每个键值对作为一个数组中的一个元素,形式为 [key, value]。
- Object.hasOwnProperty(prop): 判断对象是否具有指定属性(不包括原型链上的属性)。
- Object.assign(target, ...sources): 将一个或多个源对象的所有可枚举属性复制到目标对象,并返回目标对象。
- Object.entries(obj): 将对象转换为包含其键值对的数组。
- Object.fromEntries(iterable): 将包含键值对的数组转换为对象。
- Object.hasOwnProperty(): 检查对象是否具有指定的属性作为自己的属性,而不是继承来的。
Set 集合
Set
对象在 ECMAScript 6(ES6)中引入,为 JavaScript 带来了集合数据结构的概念,类似于数学中的集合,它允许存储一组不重复的元素。与数组相比, Set
中的值必须是唯一的,而数组可以有重复的值。
Set 的基本特性有
- 唯一性:
Set
中的每个值都是唯一的,这意味着一旦一个值被加入,尝试再次加入相同的值将不会有任何效果。 - 无序性:
Set
中的元素没有特定的顺序,迭代顺序也是不确定的。 - 可遍历:
Set
对象是可遍历的,意味着你可以使用forEach
、for...of
等循环结构来遍历集合中的元素。
主要方法:
- add(value):向
Set
中添加一个值。 - delete(value):从
Set
中删除一个值。 - has(value):检查
Set
中是否存在某个值。 - clear():删除
Set
中的所有值。 - size:返回
Set
中元素的数量。
Set
在以下场景中非常有用:
- 去重:当你需要去除数组中的重复元素时
- 映射关系:当你需要表示一对一对的数据关系时,比如键值对
- 缓存数据:设置过期时间,当数据超时时,使用
Set
的delete
方法来移除元素
Set
在 React 中的用法
jsx
function useSet(initialValue) {
const getInitValue = () => new Set(initialValue);
const [set, setSet] = useState(getInitValue);
const add = (key) => {
if (set.has(key)) {
return;
}
setSet((prevSet) => {
const temp = new Set(prevSet);
temp.add(key);
return temp;
});
};
const has = (key) => set.has(key);
const remove = (key) => {
if (!set.has(key)) {
return;
}
setSet((prevSet) => {
const temp = new Set(prevSet);
temp.delete(key);
return temp;
});
};
const reset = () => setSet(getInitValue());
return [
set,
{
add: useMemoizedFn(add),
has: useMemoizedFn(has),
remove: useMemoizedFn(remove),
reset: useMemoizedFn(reset),
},
];
}
Map
JavaScript 中的 Map
是一种键值对集合的数据结构,它与 Set
一起从 ES6(ECMAScript 2015)标准中引入的。Map对象与传统的对象(Object)类似,也是用于存储键值对,但它们之间存在一些重要的区别
在 Map 中,键可以是任何类型的值,包括数字、字符串、对象、函数、符号等,而不仅仅是字符串,这使得 Map 比 Object 更灵活。Map中的每个键都是唯一的,如果尝试插入一个已经存在的键,该键对应的值将被更新。Map对象会按照键的插入顺序保存键值对。这意味着当你遍历Map时,你会按照键插入的顺序来获取键值对。
Map 的方法:
- set(key, value):设置键对应的值。如果键已经存在,则更新其对应的值。
- get(key):获取键对应的值。如果键不存在,返回undefined。
- has(key):检查Map中是否存在指定的键。
- delete(key):删除指定的键及其对应的值。
- clear():清除Map中的所有键值对。
- size:获取Map中的键值对数量。
- forEach(callback, thisArg):遍历Map中的每个键值对。
- keys():返回一个新的迭代器,它按插入顺序包含Map中的所有键。
- values():返回一个新的迭代器,它按插入顺序包含Map中的所有值。
- entries():返回一个新的迭代器,它按插入顺序包含Map中的所有[key, value]数组
Map 的应用场景
Map数据结构在JavaScript中有很多应用场景,比如:
- 作为对象属性的映射。
- 存储数据库的索引。
- 缓存对象。
- 代替数组,当你需要维护键值对顺序时。
- 实现观察者模式等。