Skip to content

对象序列化与反序列化

用户绘制了一个复杂的图形作品,关闭浏览器前如何保存?下次打开时如何恢复?

答案是:序列化与反序列化


1. 应用场景

序列化(将对象转换为JSON)的典型应用:

  • 保存与加载:将画布保存到本地存储或服务器
  • 撤销重做:保存历史状态
  • 导出功能:导出为JSON文件
  • 协同编辑:在网络间传输对象数据

2. 序列化设计

哪些属性需要序列化?

需要

  • 几何属性(位置、尺寸、旋转、缩放)
  • 样式属性(颜色、边框、透明度)

不需要

  • 临时状态(_dirtyselected
  • 对象引用(canvas
  • 事件监听器(events
  • 方法函数

JSON 格式设计

json
{
  "type": "Rectangle",
  "id": "object_1",
  "left": 100,
  "top": 100,
  "width": 200,
  "height": 150,
  "fill": "#ff0000",
  "stroke": "#000000",
  "strokeWidth": 2,
  "rotation": 0,
  "scaleX": 1,
  "scaleY": 1,
  "opacity": 1
}

关键:必须包含 type 字段,用于反序列化时创建正确的类实例。


3. 实现 toJSON 方法

javascript
class BaseObject {
  // ... 其他代码 ...
  
  toJSON() {
    return {
      type: this.type,
      id: this.id,
      left: this.left,
      top: this.top,
      width: this.width,
      height: this.height,
      rotation: this.rotation,
      scaleX: this.scaleX,
      scaleY: this.scaleY,
      fill: this.fill,
      stroke: this.stroke,
      strokeWidth: this.strokeWidth,
      opacity: this.opacity,
      visible: this.visible
    };
  }
}

// 使用
const rect = new Rectangle({
  left: 50,
  top: 50,
  width: 100,
  height: 80,
  fill: 'red'
});

const json = rect.toJSON();
console.log(JSON.stringify(json, null, 2));

4. 反序列化:从 JSON 恢复对象

工厂模式

根据 type 字段创建对应的类实例:

javascript
class ObjectFactory {
  static create(json) {
    const constructors = {
      'Rectangle': Rectangle,
      'Circle': Circle,
      'Text': Text
      // 可扩展
    };
    
    const Constructor = constructors[json.type];
    if (!Constructor) {
      throw new Error(`Unknown object type: ${json.type}`);
    }
    
    return new Constructor(json);
  }
}

// 使用
const json = { type: 'Rectangle', left: 50, top: 50, fill: 'red' };
const rect = ObjectFactory.create(json);
rect.draw(ctx);

5. 序列化整个画布

javascript
class Canvas {
  // ... 其他代码 ...
  
  toJSON() {
    return {
      version: '1.0',
      width: this.canvas.width,
      height: this.canvas.height,
      objects: this.objects.map(obj => obj.toJSON())
    };
  }
  
  fromJSON(json) {
    this.objects = [];
    json.objects.forEach(objData => {
      const obj = ObjectFactory.create(objData);
      this.add(obj);
    });
    this.render();
  }
  
  saveToLocalStorage(key) {
    const json = this.toJSON();
    localStorage.setItem(key, JSON.stringify(json));
  }
  
  loadFromLocalStorage(key) {
    const jsonStr = localStorage.getItem(key);
    if (jsonStr) {
      const json = JSON.parse(jsonStr);
      this.fromJSON(json);
    }
  }
}

// 使用
canvas.add(new Rectangle({ left: 50, top: 50, fill: 'red' }));
canvas.add(new Circle({ left: 200, top: 50, radius: 40, fill: 'blue' }));

// 保存
canvas.saveToLocalStorage('my-canvas');

// 加载
canvas.loadFromLocalStorage('my-canvas');

6. 处理嵌套对象

如果有分组(Group),它包含子对象:

javascript
class Group extends BaseObject {
  constructor(options = {}) {
    super(options);
    this.type = 'Group';
    this.objects = options.objects || [];
  }
  
  toJSON() {
    return {
      ...super.toJSON(),
      objects: this.objects.map(obj => obj.toJSON())
    };
  }
  
  static fromJSON(json) {
    const group = new Group(json);
    group.objects = json.objects.map(objData => ObjectFactory.create(objData));
    return group;
  }
}

// 注册到工厂
ObjectFactory.constructors['Group'] = Group;

7. 版本兼容

在格式中包含版本号,支持向后兼容:

javascript
toJSON() {
  return {
    version: '1.0',
    type: this.type,
    // ... 属性
  };
}

static fromJSON(json) {
  if (json.version === '1.0') {
    // 按 1.0 格式解析
  } else if (json.version === '2.0') {
    // 按 2.0 格式解析
  }
}

8. 处理特殊情况

循环引用

如果对象间有循环引用,JSON.stringify 会报错。使用第三方库如 flatted

javascript
import { stringify, parse } from 'flatted';

const json = stringify(canvas.toJSON());
const data = parse(json);

函数属性

函数无法序列化。可以序列化函数名,反序列化时重新绑定:

javascript
toJSON() {
  return {
    // ...
    animationType: this.animationFn?.name  // 保存函数名
  };
}

9. 导出与导入文件

javascript
class Canvas {
  exportToFile(filename) {
    const json = JSON.stringify(this.toJSON(), null, 2);
    const blob = new Blob([json], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    
    URL.revokeObjectURL(url);
  }
  
  importFromFile(file) {
    const reader = new FileReader();
    reader.onload = (e) => {
      const json = JSON.parse(e.target.result);
      this.fromJSON(json);
    };
    reader.readAsText(file);
  }
}

// 使用
document.getElementById('export').addEventListener('click', () => {
  canvas.exportToFile('my-design.json');
});

document.getElementById('import').addEventListener('change', (e) => {
  const file = e.target.files[0];
  canvas.importFromFile(file);
});

10. 性能优化

大量对象时,序列化可能耗时。优化策略:

  • 增量序列化:只序列化变化的对象
  • 压缩:使用 gzip 压缩 JSON 数据
  • Web Worker:在后台线程进行序列化
javascript
// 在 Worker 中序列化
const worker = new Worker('serialize-worker.js');
worker.postMessage(canvas.toJSON());
worker.onmessage = (e) => {
  const jsonStr = e.data;
  localStorage.setItem('canvas', jsonStr);
};

本章小结

序列化让图形数据可以持久化和传输:

  • toJSON:将对象转换为 JSON
  • fromJSON:从 JSON 恢复对象
  • 工厂模式:根据 type 创建实例
  • 版本控制:支持格式演进

下一章,我们将实现对象集合与容器,管理多个图形对象。

对象序列化与反序列化 has loaded