You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何简便实现localStorage与JS数据同步并保留数据类型?

自动同步数组与localStorage,保留原生操作体验

这个场景我太熟悉了!之前做本地记事本项目的时候,手动同步数组到localStorage简直是噩梦——每次push、修改都要写重复的代码,还经常忘。其实用ES6的Proxy代理就能完美解决,既能自动同步,又完全保留JS原生数组的操作习惯,甚至连嵌套对象的修改都能搞定!

核心思路

Proxy可以拦截数组的所有操作(包括push、pop、splice、直接索引赋值、删除元素等),我们只需要在拦截器里完成「执行原生操作 → 同步到localStorage」的流程,就能实现自动同步。而且读取的时候从localStorage反序列化,保证页面刷新后数据不丢失。

基础实现:数组级自动同步

先写一个通用的创建同步数组的函数,基础版就能覆盖大部分场景:

function createSyncArray(storageKey, initialValue = []) {
  // 优先从localStorage读取已有数据,没有则用默认值
  let storedData = localStorage.getItem(storageKey);
  let array = storedData ? JSON.parse(storedData) : [...initialValue];

  // Proxy拦截器配置
  const handler = {
    // 拦截所有设置操作(比如push、arr[0] = xxx)
    set(target, prop, value) {
      // 先执行原生数组操作,保证行为和普通数组一致
      const result = Reflect.set(target, prop, value);
      // 同步最新数组到localStorage
      localStorage.setItem(storageKey, JSON.stringify(target));
      return result;
    },
    // 拦截删除操作(比如delete arr[1])
    deleteProperty(target, prop) {
      const result = Reflect.deleteProperty(target, prop);
      localStorage.setItem(storageKey, JSON.stringify(target));
      return result;
    }
  };

  // 返回被代理后的数组
  return new Proxy(array, handler);
}

怎么用?

就像用普通数组一样简单,所有操作都会自动同步:

// 创建和localStorage中'objects'键绑定的同步数组
const arrayOfObjects = createSyncArray('objects', [{id: 1, name: '初始项'}]);

// 原生操作自动同步
arrayOfObjects.push({id: 2, name: '新增项'}); // 自动同步到localStorage
arrayOfObjects.pop(); // 删除最后一项,自动同步

进阶优化:支持嵌套对象自动同步

基础版只能拦截数组本身的操作,如果直接修改数组里的对象属性(比如arrayOfObjects[0].name = 'xxx'),Proxy不会触发拦截。我们可以给数组里的每个对象也套上Proxy,实现嵌套同步:

// 给单个对象创建同步代理
function createSyncObject(obj, storageKey, parentArray) {
  return new Proxy(obj, {
    set(target, prop, value) {
      const result = Reflect.set(target, prop, value);
      // 对象修改后,同步整个父数组到localStorage
      localStorage.setItem(storageKey, JSON.stringify(parentArray));
      return result;
    }
  });
}

// 升级后的createSyncArray
function createSyncArray(storageKey, initialValue = []) {
  let storedData = localStorage.getItem(storageKey);
  
  // 初始化时给每个对象都套上同步代理
  let array = storedData 
    ? JSON.parse(storedData).map(item => 
        typeof item === 'object' && item !== null ? createSyncObject(item, storageKey, array) : item
      )
    : initialValue.map(item => 
        typeof item === 'object' && item !== null ? createSyncObject(item, storageKey, array) : item
      );

  const handler = {
    set(target, prop, value) {
      // 如果新增的元素是对象,先套上同步代理
      if (typeof value === 'object' && value !== null) {
        value = createSyncObject(value, storageKey, target);
      }
      const result = Reflect.set(target, prop, value);
      localStorage.setItem(storageKey, JSON.stringify(target));
      return result;
    },
    deleteProperty(target, prop) {
      const result = Reflect.deleteProperty(target, prop);
      localStorage.setItem(storageKey, JSON.stringify(target));
      return result;
    }
  };

  return new Proxy(array, handler);
}

现在修改嵌套对象的属性也会自动同步了:

arrayOfObjects[0].name = '自动同步的修改'; // 这行代码会触发同步!

注意事项

  1. 特殊数据类型处理:JSON序列化不支持DateMapSet、函数这些类型,会丢失或被转换。如果需要保留这些类型,可以在序列化/反序列化时加自定义处理器:
    // 序列化时处理Date
    localStorage.setItem(storageKey, JSON.stringify(target, (key, value) => {
      if (value instanceof Date) {
        return { __type: 'Date', value: value.toISOString() };
      }
      return value;
    }));
    
    // 反序列化时还原Date
    let array = storedData ? JSON.parse(storedData, (key, value) => {
      if (value?.__type === 'Date') {
        return new Date(value.value);
      }
      return value;
    }) : [...initialValue];
    
  2. 性能优化:如果数组特别大,每次全量序列化会有性能开销。可以考虑只同步修改的部分,但中小型数组完全不需要担心这个问题。
  3. 多页面同步:如果同一个域名下多个页面共用这个数据,其他页面不会自动感知变化。可以监听storage事件,当localStorage被其他页面修改时,自动更新当前页面的数组:
    window.addEventListener('storage', (e) => {
      if (e.key === 'objects') {
        // 替换现有数组内容
        const newData = JSON.parse(e.newValue);
        arrayOfObjects.length = 0;
        arrayOfObjects.push(...newData);
      }
    });
    

内容的提问来源于stack exchange,提问作者sigmaxf

火山引擎 最新活动