json.stringify 配合 json.parse 实现深拷贝时函数会被完全忽略,因 json 标准仅支持字符串、数字、布尔值、null、数组、对象六种类型;替代方案包括 structuredclone()、手写递归 deepclone 函数(支持函数、undefined、循环引用)及 lodash 自定义 customizer。

JSON.stringify 配合 JSON.parse 实现深拷贝时,函数(Function)会被完全忽略——因为 JSON 标准本身不支持函数序列化。这不是 bug,而是设计使然。若对象中包含方法(如 greet() {}、箭头函数、事件处理器等),用该方式拷贝后,这些方法在副本中将不复存在,调用会返回 undefined。
为什么函数会丢失?
JSON 是一种纯数据交换格式,只定义了 6 种可表示类型:字符串、数字、布尔值、null、数组、对象。函数不属于其中任何一类。执行 JSON.stringify({ fn: () => {} }) 时,fn 键值对直接被跳过,最终结果是 {}。
哪些替代方案能保留函数?
如果必须保留方法逻辑,不能只靠 JSON 方案,需选择支持函数克隆的机制:
-
structuredClone()(推荐优先尝试):现代浏览器和 Node.js 18.13+ / 20.6+ 原生支持,可克隆函数、Date、RegExp、Map、Set、BigInt 等,但仍不复制函数体(注意:它克隆的是函数引用,不是新函数;若原函数依赖
this或闭包,行为可能变化) -
手写递归 deepClone 函数:检测到函数类型时,直接赋值(或用
bind({})创建无上下文副本),同时处理数组、普通对象、null、基本类型。可保留原型链(用Object.getPrototypeOf()和Object.setPrototypeOf())、undefined、Symbol 等 -
第三方库定制使用:Lodash 的
_.cloneDeep()默认不保留函数;但可通过自定义customizer回调,在遇到函数时返回其自身(例如return typeof value === 'function' ? value : undefined),实现“带函数的深拷贝”
简单可用的手写函数示例
以下代码能保留函数、undefined、原始类型,并避免循环引用(使用 WeakMap 记录已拷贝对象):
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
const clone = typeof obj === 'function' ? obj : Array.isArray(obj) ? [] : {};
seen.set(obj, clone);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key], seen);
}
}
return clone;
}
调用后,copy.greet 仍是可执行函数,且不污染原对象。
要不要真的“深拷贝函数”?
多数业务场景中,函数本身无需深拷贝——它通常是无状态的工具逻辑,共享更安全高效。真正需要隔离的是函数所依赖的内部状态(如闭包变量、this 绑定)。此时更合理的做法是:
• 显式绑定上下文(fn.bind(context))
• 将状态抽离为独立数据对象,再用 JSON 拷贝该数据
• 使用类实例 + 构造器初始化,而非依赖对象上挂载的方法











