前端八股4---浅拷贝 vs 深拷贝

一、核心区别速览

对比维度 浅拷贝 深拷贝
复制层级 只复制第一层 递归复制所有层级
嵌套对象 共享内存,修改互相影响 独立内存,修改互不影响
性能 慢(数据量大时)
实现难度 简单 复杂(需处理各种边界情况)

二、代码演示区别

javascript 复制代码
// 原始对象
const original = {
  name: '张三',
  age: 18,
  address: {
    city: '北京',
    district: '朝阳区'
  },
  hobbies: ['读书', '游泳']
};

// ========== 浅拷贝 ==========
const shallow = { ...original };

shallow.name = '李四';           // ✅ 修改第一层,不影响原对象
shallow.address.city = '上海';   // ❌ 修改嵌套对象,原对象也被改了!

console.log(original.address.city); // '上海' - 被影响了!
console.log(original.name);          // '张三' - 没被影响

// ========== 深拷贝 ==========
const deep = JSON.parse(JSON.stringify(original));

deep.name = '王五';              // ✅ 不影响原对象
deep.address.city = '广州';      // ✅ 不影响原对象
deep.hobbies.push('跑步');       // ✅ 不影响原对象

console.log(original.address.city); // '北京' - 没被影响
console.log(original.hobbies);      // ['读书', '游泳'] - 没被影响

三、浅拷贝的 6 种方式

3.1 对象浅拷贝

javascript 复制代码
const obj = { a: 1, b: { c: 2 } };

// 1. 展开运算符(最常用)
const copy1 = { ...obj };

// 2. Object.assign
const copy2 = Object.assign({}, obj);

// 3. Object.create + 遍历(少见)
const copy3 = Object.create(Object.getPrototypeOf(obj));
Object.assign(copy3, obj);

3.2 数组浅拷贝

javascript 复制代码
const arr = [1, 2, { a: 3 }];

// 1. 展开运算符
const copy1 = [...arr];

// 2. slice()
const copy2 = arr.slice();

// 3. concat()
const copy3 = arr.concat();

// 4. Array.from()
const copy4 = Array.from(arr);

// 5. 循环遍历
const copy5 = [];
for (let i = 0; i < arr.length; i++) {
  copy5[i] = arr[i];
}

3.3 特殊情况

javascript 复制代码
// 类数组对象
const args = { 0: 'a', 1: 'b', length: 2 };
const copy = Array.prototype.slice.call(args);

// Map 和 Set(只浅拷贝)
const map = new Map([['key', { value: 1 }]]);
const mapCopy = new Map(map);

四、深拷贝的完整方案

4.1 JSON 大法(最常用,但有缺陷)

javascript 复制代码
const deepCopy = JSON.parse(JSON.stringify(obj));

✅ 优点:

  • 简单易用,一行代码

  • 性能好(V8 引擎优化)

❌ 缺点:

javascript 复制代码
const obj = {
  // 1. 函数会被忽略
  fn: function() { console.log('hello'); },
  
  // 2. undefined 会被忽略
  undef: undefined,
  
  // 3. Symbol 会被忽略
  sym: Symbol('id'),
  
  // 4. 日期会变成字符串
  date: new Date(),  // 变成 "2024-01-01T00:00:00.000Z"
  
  // 5. 正则变成空对象
  reg: /abc/g,       // 变成 {}
  
  // 6. NaN、Infinity 变成 null
  nan: NaN,          // 变成 null
  inf: Infinity,     // 变成 null
  
  // 7. 循环引用会报错
  circular: null
};
obj.circular = obj;  // ❌ TypeError: Converting circular structure to JSON

console.log(JSON.parse(JSON.stringify(obj)));
// 结果:{ date: "2024-...", reg: {}, nan: null, inf: null }
// fn、undef、sym 都不见了

4.2 手写递归深拷贝(面试常考)

javascript 复制代码
function deepClone(obj, hash = new WeakMap()) {
  // 处理 null 和基本类型
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  // 处理日期
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // 处理正则
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  
  // 处理数组和对象
  const cloneObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, cloneObj);
  
  // 递归复制所有属性(包括 Symbol 和不可枚举属性)
  Reflect.ownKeys(obj).forEach(key => {
    cloneObj[key] = deepClone(obj[key], hash);
  });
  
  return cloneObj;
}

// 测试
const obj = {
  name: '张三',
  age: 18,
  address: { city: '北京' },
  date: new Date(),
  reg: /abc/g,
  fn: () => {},
  sym: Symbol('id')
};
obj.self = obj;  // 循环引用

const cloned = deepClone(obj);
console.log(cloned.address !== obj.address);  // true
console.log(cloned.date instanceof Date);     // true
console.log(cloned.reg instanceof RegExp);    // true
console.log(cloned.self === cloned);          // true(循环引用正确处理)

4.3 lodash 库(生产环境推荐)

javascript 复制代码
import _ from 'lodash';

const deepCopy = _.cloneDeep(obj);

4.4 structuredClone(现代浏览器原生支持)

javascript 复制代码
// 2022 年新增的原生深拷贝 API
const deepCopy = structuredClone(obj);

// 支持大多数类型,但仍不支持函数、Symbol、DOM 节点等

4.5 MessageChannel(异步深拷贝)

javascript 复制代码
function deepCloneAsync(obj) {
  return new Promise((resolve) => {
    const { port1, port2 } = new MessageChannel();
    port1.onmessage = (e) => resolve(e.data);
    port2.postMessage(obj);
  });
}

// 使用
const cloned = await deepCloneAsync(obj);

五、各方案对比

方案 优点 缺点 适用场景
JSON 大法 简单、快 不支持函数/undefined/Symbol/循环引用 纯数据对象
手写递归 完全可控、支持所有类型 代码量大、需处理边界 面试、特殊需求
lodash 功能完善、稳定 需要引入库 生产环境
structuredClone 原生、快 兼容性要求高、不支持函数 现代浏览器
MessageChannel 支持循环引用 异步、性能差 特殊场景

六、面试高频问题

Q1:浅拷贝和深拷贝的区别?

答:

浅拷贝只复制对象的第一层属性,对于嵌套的引用类型数据,新旧对象仍然共享同一块内存,修改嵌套数据会互相影响。深拷贝会递归复制所有层级,完全开辟新的内存空间,新旧对象彻底独立,修改互不影响。

Q2:JSON.parse(JSON.stringify()) 有什么缺陷?

答:

  1. 无法复制函数、undefined、Symbol 等类型(会被忽略)

  2. 日期对象会变成字符串

  3. 正则对象会变成空对象

  4. NaN、Infinity 会变成 null

  5. 循环引用会报错

Q3:如何实现一个完美的深拷贝?

答:

需要考虑以下情况:

  1. 基本类型直接返回

  2. 处理循环引用(使用 WeakMap)

  3. 特殊对象:Date、RegExp、Map、Set 等

  4. 函数(一般不需深拷贝,可直接返回原引用)

  5. 数组和普通对象的递归复制

  6. Symbol 和不可枚举属性的处理

Q4:浅拷贝有哪些方式?

答:

  • 对象:{...obj}Object.assign({}, obj)

  • 数组:[...arr]arr.slice()arr.concat()Array.from(arr)

相关推荐
用户新3 小时前
V8引擎 精品漫游指南--Ignition篇(下 一) 动态执行前的事情
前端·javascript
阿里嘎多学长3 小时前
2026-04-30 GitHub 热点项目精选
开发语言·程序员·github·代码托管
叶小鸡4 小时前
Java 篇-项目实战-苍穹外卖-笔记汇总
java·开发语言·笔记
@PHARAOH4 小时前
WHAT - GitLens vs Fork
前端
yqcoder5 小时前
前端性能优化:如何减少重绘与重排?
前端·性能优化
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题】【Java基础篇】第22题:HashMap 和 HashSet 有哪些区别
java·开发语言·哈希算法·散列表·hash
时空系5 小时前
第10篇:继承扩展——面向对象编程进阶 python中文编程
开发语言·python·ai编程
洋子5 小时前
Yank Note 系列 13 - 让 AI Agent 进入笔记工作流
前端·人工智能
CHANG_THE_WORLD6 小时前
python 批量终止进程exe
开发语言·python
古城小栈6 小时前
从 cargo-whero 库中,找到提升 rust 的契机
开发语言·后端·rust