跟curosr学会什么api

前言

目前在工作中会大量使用 cursor 作为主力编辑器,发现大模型很多边际条件和性能优化处理的都很优雅,用到了很多之前没有去用过的 ES6 方法;这里记录下跟 cursor 都学会了哪些更优雅的实现。

Set

Set是一个类数组的数据结构,用于存储唯一值,Set对象允许你存储任何类型的值,包括对象和原始值。

用法: const set = new Set([1, 2, 3]);

javascript 复制代码
const set = new Set([1, 2, 3]);
// 操作数据方法
set.add(4);
set.delete(1);
set.has(4);
set.clear();

//遍历方法 Set的遍历顺序就是插入顺序
// keys(),values(),entries() 方法返回的都是遍历器对象 , Set 结构没有键名,只有键值所以valus 和 keys方法返回相同。
set.keys();
set.values();
set.entries();
set.forEach((value, key) => console.log(key + " : " + value));
// 扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构;所以通过遍历器数组方法也可以同样作用于set数据。
let arr = [...set];

add 和 delete 方法

向 Set 加入值的时候,不会发生类型转换,所以 5 和"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做"Same-value-zero equality",它类似于精确相等运算符(===)严格相等比较的是引用(即内存地址)。 加入两个对象 set 会认为不相等

javascript 复制代码
let set = new Set();
set.add({});
set.size; // 1
set.add({});
set.size; // 2

因为 Set 的这种引用存储特性(通过内存地址而非内容识别对象),它特别适合维护任务函数队列: 当某个函数执行后,可以直接用 delete() 方法精准删除队列中的该函数,无需担心因内容相同而误删其他函数。

javascript 复制代码
// 创建任务队列
const taskQueue = new Set();

// 定义任务函数(必须是具名函数或变量引用)
const task1 = () => console.log("执行任务1");
const task2 = () => console.log("执行任务2");
const task3 = () => console.log("执行任务3");

// 添加任务到队列
taskQueue.add(task1);
taskQueue.add(task2);
taskQueue.add(task3);

// 执行并删除任务的函数
const runAndRemove = (task) => {
  if (taskQueue.has(task)) {
    task(); // 执行任务
    taskQueue.delete(task); //删除当前任务
  }
};

// 执行任务1并删除
runAndRemove(task1);
console.log("当前队列长度:", taskQueue.size); // 2
console.log("剩余任务:", [...taskQueue]); // [task2, task3]

// 尝试删除新创建的相同内容函数
const fakeTask = () => console.log("执行任务2");
taskQueue.delete(fakeTask); // 不会删除真正的 task2
console.log("安全防护后长度:", taskQueue.size); // 2(未变化)

Set 与 Object 在管理任务队列上的对比 Set 的优势

  1. 引用精确性
  2. 内存自动管理
  3. 操作简洁性 操作复杂度 O(1) vs Object 的 O(n)
  4. 遍历效率更高 缺点
  5. 元数据存储限制
  6. 兼容性

所以简单场景使用Set,复杂场景可以Set配合Map实现。

Map

Map 和 WeakMap 之前在 React 和 Lit 的源码中就看到过大量使用,项目中可以用起来更优雅的管理数据。

Map 类似对象,Map 的键(key)可以为任何值(包括函数、对象或任何原始值); 序列化和解析:没有对序列化或解析的原生支持。 (但你可以通过使用 JSON.stringify() 及其 replacer 参数和 JSON.parse() 及其 reviver 参数来为 Map 构建自己的序列化和解析支持。参见 Stack Overflow 问题 How do you JSON.stringify an ES6 Map?) 前往 MDN 查看

任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作 Map 构造函数的参数。这就是说,Set 和 Map 都可以用来生成新的 Map。 通过下面的例子可以看出数组的第三项并不会加入Map,set同样可以作为 Map 的键。 Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

javascript 复制代码
// Map(2) {'name' => '张三', 'title' => 'Author'}
const map = new Map([
  ["name", "张三"],
  ["title", "Author"],
]);
// Map(2) {'name' => '张三', 'title' => 'Author'}
const map1 = new Map([
  ["name", "张三", "2333"],
  ["title", "Author"],
]);
//Map(3) {'name' => '张三', 'title' => 'Author', Set(2) => undefined}
const set = new Set([11, 22]);
map1.set(set);

基础使用方法

javascript 复制代码
//根据内存地址查找
//Map.prototype.set(key, value)
let map = new Map().set(1, "a").set(2, "b");
map.set("foo", true);

//Map.prototype.get(key)
m.get("foo");

//Map.prototype.has(key)
m.has("foo");

//Map.prototype.delete(key)
m.has("foo");

//Map.prototype.clear()
map.clear();

//size 返回数量
map.size;

遍历方法

Map 的遍历顺序就是插入顺序。

javascript 复制代码
const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

//Map 结构转为数组结构

[...map.keys()]
//['F', 'T']

[...map.values()]
// ['no', 'yes']

[...map.entries()]
// [['F', 'no'] , ['T', 'yes']]

[...map]
// [['F', 'no'] , ['T', 'yes']]


// forEach
map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

Map、Object、JSON

Map 转为对象 所有 Map 的键都是字符串,它可以无损地转为对象 ,有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

javascript 复制代码
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k, v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map().set("yes", true).set("no", false);
strMapToObj(myMap);

对象转为 Map

javascript 复制代码
let obj = { a: 1, b: 2 };
let map = new Map(Object.entries(obj));

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}
objToStrMap({ yes: true, no: false });

Map 转为 JSON

javascript 复制代码
function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set("yes", true).set("no", false);
strMapToJson(myMap);

Map 实际应用

通常在开发前端用户监控SDK中很适合使用 Map 作为数据结构,因为存在复杂数据需要记录,并且严格维护插入顺序; 或者也可以用来记录用户操作链路(独立上报和整体上报)作为整体上报部分。

javascript 复制代码
class ErrorTracker {
    constructor() {
        // 使用 Map 存储错误信息,key 为错误实例,value 为附加的上下文信息
        this.errorContexts = new Map();
        
        // 初始化错误监控
        window.addEventListener('error', this.captureError.bind(this));
    }

    // 捕获错误并添加上下文
    captureError(event) {
        const error = event.error || event;
        const context = {
            timestamp: Date.now(),
            url: window.location.href,
            userAgent: navigator.userAgent,
            stack: error.stack || 'No stack trace'
        };

        // 将错误对象作为键存储上下文(避免字符串序列化问题)
        this.errorContexts.set(error, context);
        
        // 实际SDK中这里会上报到服务器
        console.error('Captured error:', error, 'Context:', context);
    }

    // 添加自定义上下文到已有错误
    enrichError(error, extraData) {
        if (this.errorContexts.has(error)) {
            const context = this.errorContexts.get(error);
            this.errorContexts.set(error, { ...context, ...extraData });
        }
    }

    // 获取所有错误报告(按发生顺序)
    getErrorReports() {
        return Array.from(this.errorContexts.entries()).map(([error, context]) => ({
            name: error.name,
            message: error.message,
            ...context
        }));
    }
}

// 使用示例
const monitor = new ErrorTracker();

// 模拟错误
try {
    throw new Error('Demo error');
} catch (err) {
    // 捕获后添加业务上下文
    monitor.enrichError(err, { 
        userId: 'u123', 
        component: 'CheckoutPage' 
    });
}

// 获取按时间排序的错误报告
console.log(monitor.getErrorReports());

高频增删键值对的情况下适合比 Object 的增删性能更好

WeakMap

了解WeakMap看到手写cloneDeep时候看到的。

WeakMap结构与Map结构类似,也是用于生成键值对的集合。 阮一峰ES6入门 WeakMap与Map的区别有两点: 首先,WeakMap只接受对象(null除外)和 Symbol 值作为键名,不接受其他类型的值作为键名。 其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。 基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。

这段代码就很好利用WeakMap的特性。

javascript 复制代码
function cloneDeep(source, hash = new WeakMap()) {
  if (!isObject(source)) return source
  if (hash.has(source)) return hash.get(source)

  var target = Array.isArray(source) ? [] : {}
  hash.set(source, target)

  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (isObject(source[key])) {
        target[key] = cloneDeep(source[key], hash)
      } else {
        target[key] = source[key]
      }
    }
  }
  return target
}
相关推荐
独立开阀者_FwtCoder10 分钟前
TypeScript 杀疯了,开发 AI 应用新趋势!
前端·javascript·github
汪子熙15 分钟前
QRCode.js:一款轻量级、跨浏览器的 JavaScript 二维码生成库
前端·javascript·面试
Mintopia16 分钟前
Three.js 阴影映射:光影魔术师的神秘配方
前端·javascript·three.js
Mintopia18 分钟前
计算机图形学法线贴图(Normal Mapping)教学:让平面物体 “穿上魔法铠甲”
前端·javascript·计算机图形学
独立开阀者_FwtCoder18 分钟前
Node.js 官方发布新工具,助力稳定 TypeScript 支持!
前端·javascript·vue.js
洛千陨19 分钟前
vue + LogicFlow 实现流程图展示
前端·javascript
小小神仙20 分钟前
JSCommon系列 - 前端常用工具库集合
前端·javascript·面试
cv也要开心20 分钟前
Commander.js 完全掌握——构建优雅CLI
前端·javascript
FogLetter25 分钟前
从电影应用页面中学习模块化思想:打造优雅的前端架构
前端·javascript·代码规范
__Yx__27 分钟前
JavaScript核心概念输出——this指向
javascript