JavaScript 对象深拷贝方法全解析:从 JSON 到 MessageChannel

作者:青柠

时间:2026-10-25

浏览:455

本文系统总结了JavaScript中实现对象深拷贝的多种方案。首先分析JSON.parse/stringify的局限性与适用场景,接着介绍Lodash、AngularJS等主流库的克隆功能。此外,详细讲解了ES6浅拷贝机制、原生手写递归深拷贝以及基于MessageChannel的异步深拷贝实现,帮助开发者根据实际需求选择合适方案。

文章目录

快速克隆(存在数据丢失问题) – JSON.parse/stringify

若对象中不包含Date、函数、undefined、Infinity、正则表达式、Map、Set、Blob、FileList、ImageData等复杂类型,可使用极简的一行代码实现深克隆。

该方法主要存在以下问题:

  • 会忽略 undefined
  • 会忽略 symbol
  • 无法序列化函数
  • 无法处理循环引用的对象
JSON.parse(JSON.stringify(object))
const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

使用第三方框架

由于对象克隆涉及复杂类型、循环引用及函数处理,难度较高,主流框架通常已内置克隆功能。建议优先检查所用库是否提供相关支持,避免重复造轮子。例如:

  • lodash cloneDeep;可通过单独导入lodash.cloneDeep模块使用。若尚未使用lodash,它是一个极佳的选择。
  • AngularJS angular.copy
  • jQuery – jQuery.extend(true, { }, oldObject); .clone() 仅能克隆DOM元素

ES6

为求完整,需注意ES6提供了两种浅拷贝机制:Object.assign()与展开运算符语法。它们将源对象所有可枚举的自身属性值复制到目标对象。例如:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // 拓展运算符

原生实现深拷贝

手写原生深拷贝代码在面试中常见。尽管实际项目中较少直接使用,但掌握其核心思想至关重要,因为面试中频繁考察此类实现。

function deepClone(obj) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非对象')
  }

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj }
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })

  return newObj
}

let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3
  }
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj.b.c) // 2

MessageChannel

MessageChannel 提供了一种实现深拷贝的途径。

function structuralClone(obj) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel()
    port2.onmessage = ev => resolve(ev.data)
    port1.postMessage(obj)
  })
}

var obj = {
  a: 1,
  b: {
    c: 2
  }
}

obj.b.d = obj.b

// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
  const clone = await structuralClone(obj)
  console.log(clone)
}
test()

更多关于js中对象深拷贝方法请查看下面的相关链接

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章