【js】JavaScript高级数据结构策略

发布于:2025-07-31 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言

在JavaScript中,数据结构策略是指根据问题需求选择合适的数据结构(如数组、对象、Set、Map等)来高效地存储和操作数据。不同的数据结构有不同的特点和适用场景。

大多数情况下我都是依赖数组、对象和基本循环来解决项目编码工作。

基础数据结构选择策略

1、数组(Array)

适用于有序集合,特别是需要按索引访问元素的情况。

提供多种内置方法(如push, pop, shift, unshift, splice, slice, map, filter, reduce等)便于操作。

当需要维护元素的顺序时,数组是首选。

注意:在数组开头插入或删除元素(shift和unshift)的时间复杂度为O(n),因为需要移动所有后续元素。

// 用索引快速操作尾部
const stack = [];
stack.push(1); // O(1)
stack.pop();   // O(1)

// 避免在数组开头操作
const queue = [];
// 低效:queue.shift() → 改用链表或循环数组

2、对象(Object)

适用于存储键值对,其中键是字符串或Symbol。

可以快速访问、插入和删除属性(平均O(1))。

当需要表示实体(如用户信息)或需要根据特定键快速查找值时使用。

注意:对象键是无序的(尽管现代JavaScript引擎可能按照创建顺序维护,但不要依赖顺序)。

常见的高级数据结构策略

在构建大型系统或在性能敏感环境中工作时,这些基本的数据结构就有点不够用了。

于是几个常见的高级数据结构策略也用的越来越多了,比如经常需要不能重复的类数组类型;属性有序的对象等

1、Set

存储唯一值(无重复值)的集合。

维护插入顺序,可以快速检查值是否存在(has()方法),添加和删除值。

适用于去重或需要快速检查成员存在的场景。

const unique = new Set([1, 2, 2, 3]); // {1, 2, 3}
unique.has(2); // true (比数组的 includes 快)

2、Map

类似于对象,但键可以是任意类型(包括对象、函数等)

维护键值对的插入顺序,迭代时按插入顺序返回。

提供了更便捷的方法(如set, get,  has, delete)和属性(size)

当键不是字符串或需要维护插入顺序时,Map比对象更合适。

const map = new Map();
map.set({ id: 1 }, "User1"); // 对象作为键
map.get(key); // O(1) 访问

3、WeakMap / WeakSet

But,因为Map保留对键的强引用,即使元素从DOM中移除,也会阻止它们被垃圾回收。

在一些向DOM节点附加临时元数据:工具提示、事件监听器的情况下,尤其是在动态UI繁重的前端框架中(如React、Angular甚至一些动态生成临时dom的原生JS),如果使用标准Map将元数据与DOM元素关联,可能会导致内存泄漏。

顾名思义,与Map和Set类似,但键(WeakMap)或值(WeakSet)是弱引用,不会阻止垃圾回收。

适用于需要临时存储或避免内存泄漏的场景。

问题代码:

const metadata = new Map();
const button = document.getElementById('submit');
metadata.set(button, { clicked: true });
button.remove(); // 仍然保留在内存中!

改用WeakMap,它对键持有弱引用——允许垃圾回收清理未引用的对象:

const weakMetadata = new WeakMap();
weakMetadata.set(button, { clicked: true });
button.remove(); // 弱引用,可以被回收 

注意:不可迭代,没有`size`属性。

由此……引发思考🤔

还有哪些未被充分利用的JavaScript数据结构呢?

不常见的高级数据结构策略

1、Object.freeze()  and Object.seal()

在协作项目或大型应用中,共享配置对象可能会被意外修改,引入难以调试的副作用。当处理环境设置、Redux状态或模块之间共享的常量时,这尤其危险哦。

Object.freeze() 实现完全不可变性

Object.freeze() 方法可以冻结一个对象。被冻结的对象不能被扩展,不能添加新属性,不能删除已有属性,不能修改已有属性的可枚举性、可配置性或 writable 特性,也不能修改已有属性的值。此外,冻结一个对象也会递归地冻结该对象的所有子对象。

Object.seal() 实现部分保护

Object.seal() 方法可以密封一个对象。密封对象会阻止添加新属性,并将所有现有属性标记为不可配置(non - configurable)。属性的值仍然可以修改,前提是属性本身是可写(writable)的。

// 可变共享对象 — 危险
const config = { debug: true };
config.debug = false; // 允许这样做,可能导致隐藏的bug
// 完全不可变
const frozenConfig = Object.freeze({ debug: true });
frozenConfig.debug = false; // 失败(或在严格模式下抛出错误)
// 密封但值可变
const sealedConfig = Object.seal({ debug: true });
sealedConfig.newProp = 'x'; // 失败
sealedConfig.debug = false; // 有效

总结来说,Object.freeze() 提供了更严格的不可变性,对象的所有方面都被锁定,而 Object.seal() 主要防止对象结构的改变(添加或删除属性),同时允许对可写属性的值进行修改。

// 创建一个普通对象
let car = {
    brand: 'Toyota',
    model: 'Corolla',
    year: 2020
};

// 密封对象
Object.seal(car);

// 尝试添加新属性
car.color ='red';
console.log('color' in car); // 输出 false,添加失败

// 尝试删除属性
delete car.brand;
console.log('brand' in car); // 输出 true,删除失败

// 尝试修改属性值(如果属性是可写的)
car.year = 2021;
console.log(car.year); // 输出 2021,修改成功

// 尝试将属性标记为不可配置(已经是不可配置的,所以操作无效)
Object.defineProperty(car,'model', { configurable: false });
// 这不会抛出错误,但也不会改变任何东西,因为已经密封,属性是不可配置的

    适用于:

    • 防止Redux/全局状态中的配置被意外更改

    • 在前端和后端之间共享环境常量

    • 保护库选项不被篡改

    有助于维护代码安全,强制执行数据契约,并消除由意外修改引起的bug。

    2、Array.from()

    Array.from() 方法从一个类似数组或可迭代对象创建一个新的数组实例。

    在 JavaScript 中,NodeList 是 DOM 操作返回的类似数组的对象,但它缺少许多数组的方法。Array.from() 结合映射函数可以有效地将 NodeList 转换为真正的数组,并对其中的每个元素进行操作,从而实现更简洁、干净的代码。

    当你需要对 NodeList 中的每个元素进行操作时,可以在 Array.from() 中传入一个映射函数。映射函数会对 NodeList 中的每个元素执行,并将返回值组成新的数组。

    const divs = document.querySelectorAll('div');
    const texts = [...divs].map(div => div.textContent); // 效率低下
    
    const texts = Array.from(divs, div => div.textContent); // 高效

    适用于:

    • 转换和变换NodeListargumentsSetMap

    • 在富文本编辑器或可视化工具中解析或操作内容

    通过减少中间数组创建和方法链,提高性能和代码清晰度。

    3、Object.groupBy()

    无需Reduce地狱的原生分组,对数组数据进行分组是一项常见任务——无论是用户角色、销售报告还是API响应。大多数开发者使用冗长的reduce()逻辑或依赖Lodash等实用库。

    // 老式reduce方式
    const grouped = users.reduce((acc, user) => {
      const dept = user.department;
      (acc[dept] ||= []).push(user);
      return acc;
    }, {});
    
    //在Babel或带有polyfill的Node等现代环境中可用
    const grouped = Object.groupBy(users, user => user.department);

    适用于:

    • 按状态、类别、角色对API响应项进行分组

    • 创建仪表板、摘要报告或分析图表

    降低复杂性,提高可读性,并减少对外部库的依赖。

    总结


    JavaScript 的内置数据结构,其强大程度远超多数开发者的认知。

    当你深入探索并掌握 WeakMap、Set、Object.freeze () 的应用时机与方式,甚至将目光投向 Object.groupBy () 这类崭露头角的新特性时,就能以更优雅的方式攻克复杂难题,同时避开那些在大型应用开发过程中极易遭遇的陷阱。如此一来,你在 JavaScript 的编程之路上,将能更加从容地应对各种挑战,构建出更为健壮、高效的应用程序。


    网站公告

    今日签到

    点亮在社区的每一天
    去签到