侧边栏壁纸
博主头像
一笑痕

仙人之下我无敌,
仙人之上一换一。

  • 累计撰写 62 篇文章
  • 累计收到 7 条评论

structuredClone 和 JSON 往返不是一回事,4 种字段当场变样

2026-9-18 / 0 评论 / 2 阅读

structuredClone 和 JSON 往返不是一回事,4 种字段当场变样

复制一份嵌套数据,用 JSON.parse(JSON.stringify(x)) 还是 structuredClone(x),效果不一样。这件事很多人知道结论,但具体到某种字段到底变成什么样,未必都亲眼看过。下面把 4 种常见字段挨个跑了一遍,环境是 node v26.8.1(V8 内建 structuredClone),每个结果都能用 tools/verify-structuredclone-json.mjs 复现。

4 种字段,一张清单

[undefined]
  structuredClone -> Undefined / typeof=undefined
  JSON 往返       -> 抛错(stringify(undefined) 返回的是 undefined 不是字符串,parse 读到 undefined 直接 SyntaxError)
[date]
  structuredClone -> Date / typeof=object
  JSON 往返       -> String / typeof=string
[re]
  structuredClone -> RegExp / typeof=object
  JSON 往返       -> Object / typeof=object
[map]
  structuredClone -> Map / typeof=object
  JSON 往返       -> Object / typeof=object

四条里最要命的是第一条。字段的值是 undefined,JSON 往返不是丢值这么简单,是整个调用直接抛错,因为 JSON.stringify(undefined) 返回的不是字符串,是 undefined 本身,JSON.parse 拿到的参数类型就不对。

每条的细节

Date 那条还有个隐藏差异:new Date(0) 往返之后变成字符串 "1970-01-01T00:00:00.000Z"。值能对上,但拿到的是 string,getTime() 调不了了。代码里凡是 .getTime().getFullYear() 这类调用,JSON 往返之后一律 TypeError。

RegExp 往返之后对象身份没了。/a/gsourceflags 还在,但它是普通 Object,test()exec() 这类方法都不在了。lastIndex 这类状态属性也丢。

Map 变成普通对象,这点最容易在接口层悄悄发生。前端拿到的数据是 Map,一进 JSON 就散成对象,map.get()map.size 全失效。

还有一个没写进清单的差异:值是函数或者 Symbol 的字段,structuredClone 会直接抛 DataCloneError,把问题摆在台面上;JSON 往返则是静默跳过这个字段,复制出来的对象少一个键,不报任何错。静默的那种更难查,因为下游拿到的是「合法但残缺」的数据,报错点往往在好几层之外。

验证脚本

const cases = {
  undefined: undefined,
  date: new Date(0),
  re: /a/g,
  map: new Map([["k", 1]]),
};

for (const [name, v] of Object.entries(cases)) {
  const sc = structuredClone(v);
  let js = null;
  let jsErr = "";
  try {
    js = JSON.parse(JSON.stringify(v));
  } catch (e) {
    jsErr = `(往返抛 ${e.name})`;
  }
  const tag = (obj) => Object.prototype.toString.call(obj).slice(8, -1);
  console.log(`[${name}]`);
  console.log(`  structuredClone -> ${tag(sc)}`);
  console.log(`  JSON 往返       -> ${jsErr ? "抛错" : tag(js)}${jsErr}`);
}

什么时候用哪个

数据只在页面内存里复制一份,用 structuredClone,类型守得住。数据要发到别处(接口、localStorage、postMessage 走序列化),那本来就要过一遍序列化,JSON 往返丢掉的类型丢了也不冤,但要在边界上想清楚字段是什么类型。

不建议这么干:拿 JSON 往返当「深度复制顺便清洗数据」的技巧用。清洗是把不要的字段去掉,类型是另一件事,两件事搅在一起,哪天丢了个 Date 或者抛了个 SyntaxError,排查起来比当初省下的那几行代码贵得多。

    🤞 分享