ES6 深克隆与浅克隆详解:原理、实现与应用场景

ES6 深克隆与浅克隆详解:原理、实现与应用场景

一、克隆的本质与必要性

在 JavaScript 中,数据分为两大类型:

  • 基本类型:Number、String、Boolean、null、undefined、Symbol、BigInt
  • 引用类型:Object、Array、Function、Date、RegExp 等

克隆的必要性

const original = { a: 1, b: { c: 2 } };
const copy = original; // 引用复制original.a = 10;
console.log(copy.a); // 10 - 修改影响副本original.b.c = 20;
console.log(copy.b.c); // 20 - 深层修改也影响副本

二、浅克隆(Shallow Clone)

1. 概念与特点

  • 只复制对象的第一层属性
  • 嵌套对象仍然是引用关系
  • 适用于简单对象结构

2. ES6 实现方式

2.1 扩展运算符(…)
const obj = { a: 1, b: { c: 2 } };
const shallowClone = { ...obj };obj.b.c = 3;
console.log(shallowClone.b.c); // 3(受影响)
2.2 Object.assign()
const arr = [1, 2, { d: 4 }];
const arrClone = Object.assign([], arr);arr[2].d = 5;
console.log(arrClone[2].d); // 5(受影响)
2.3 Array.slice()
const original = [1, { name: 'Alice' }];
const clone = original.slice();original[1].name = 'Bob';
console.log(clone[1].name); // 'Bob'(受影响)

3. 使用场景

  • 组件 props 传递配置对象
  • 状态管理中的状态快照
  • 简单数据结构的临时拷贝

三、深克隆(Deep Clone)

1. 概念与特点

  • 递归复制对象的所有层级
  • 创建完全独立的内存副本
  • 修改原对象不影响克隆对象

2. 手动实现深克隆

基础递归实现:
function deepClone(source) {// 基本类型直接返回if (source === null || typeof source !== 'object') {return source;}// 处理数组if (Array.isArray(source)) {return source.map(item => deepClone(item));}// 处理对象const clone = {};for (const key in source) {if (source.hasOwnProperty(key)) {clone[key] = deepClone(source[key]);}}return clone;
}// 测试
const obj = { a: 1, b: { c: 2 } };
const deepCopy = deepClone(obj);obj.b.c = 3;
console.log(deepCopy.b.c); // 2(不受影响)
增强版(处理特殊对象):
function enhancedDeepClone(source, map = new WeakMap()) {// 基本类型直接返回if (source === null || typeof source !== 'object') {return source;}// 处理循环引用if (map.has(source)) return map.get(source);// 特殊对象处理switch (source.constructor) {case Date:return new Date(source);case RegExp:return new RegExp(source);case Set:return new Set([...source].map(item => enhancedDeepClone(item, map)));case Map:return new Map([...source].map(([k, v]) => [enhancedDeepClone(k, map), enhancedDeepClone(v, map)]));case ArrayBuffer:return source.slice(0);}// 普通对象和数组const clone = Array.isArray(source) ? [] : {};map.set(source, clone);// 处理Symbol键const symbolKeys = Object.getOwnPropertySymbols(source);const allKeys = [...Object.keys(source), ...symbolKeys];for (const key of allKeys) {clone[key] = enhancedDeepClone(source[key], map);}return clone;
}

3. JSON 序列化法(有局限)

const obj = {date: new Date(),regex: /pattern/g,func: () => console.log('test')
};const jsonClone = JSON.parse(JSON.stringify(obj));console.log(jsonClone);
// {
//   date: "2023-08-15T12:00:00.000Z", // Date转为字符串
//   regex: {},                        // RegExp变为空对象
//   func: null                        // 函数丢失
// }

JSON方法的缺陷

  1. 丢失函数属性
  2. 忽略Symbol键
  3. Date对象转为字符串
  4. RegExp变为空对象
  5. 无法处理循环引用
  6. 忽略undefined值

4. 现代浏览器原生API:structuredClone()

const obj = {date: new Date(),regex: /pattern/g,array: [1, 2, 3],set: new Set([4, 5, 6])
};const clone = structuredClone(obj);console.log(clone);
// {
//   date: Date, // 保持Date对象
//   regex: /pattern/g, // 保持正则
//   array: [1,2,3],
//   set: Set(3) {4,5,6}
// }

支持的数据类型

  • 原始值
  • Array/ArrayBuffer
  • Map/Set
  • Date/RegExp
  • Blob/File/FileList
  • ImageData/ImageBitmap
  • 错误类型(Error, EvalError等)
  • 对象字面量(仅包含以上类型)

不支持的类型

  • Function
  • DOM节点
  • 对象方法
  • 属性描述符/getter/setter
  • 原型链

四、深克隆性能优化

1. 循环引用处理

function deepClone(source, map = new WeakMap()) {// ... 其他代码if (map.has(source)) {return map.get(source);}const target = new constructor();map.set(source, target);// ... 克隆逻辑
}

2. 非递归实现(栈代替递归)

function deepCloneStack(source) {const stack = [];const map = new WeakMap();const target = new source.constructor();stack.push([source, target]);map.set(source, target);while (stack.length) {const [current, parent] = stack.pop();for (const key in current) {if (current.hasOwnProperty(key)) {const value = current[key];// 基本类型直接赋值if (value === null || typeof value !== 'object') {parent[key] = value;continue;}// 处理循环引用if (map.has(value)) {parent[key] = map.get(value);continue;}// 创建新对象const child = new value.constructor();parent[key] = child;map.set(value, child);stack.push([value, child]);}}}return target;
}

五、应用场景与最佳实践

1. 浅克隆适用场景

  • 组件 props 传递配置对象
  • Redux reducer 中的状态更新
  • 函数参数中的对象扩展
  • 无嵌套结构的简单数据对象
// Vue 组件 props 解构
export default {props: ['config'],setup(props) {const localConfig = { ...props.config }; // 浅克隆}
}

2. 深克隆必要场景

  • 状态管理中的初始状态快照
  • 需要完全隔离的缓存数据
  • 撤销/重做功能实现
  • 复杂配置对象的版本管理
  • Web Worker 数据传输
// 在 Web Worker 中处理数据
worker.postMessage(structuredClone(largeDataSet));

3. 现代框架中的克隆实践

Vue 状态管理:
// 使用深克隆创建初始状态
const initialState = deepClone(fetchedData);// 在组件中使用
export default {data() {return {localState: deepClone(this.initialState)}}
}
React 状态更新:
function reducer(state, action) {switch (action.type) {case 'UPDATE_USER':// 深合并示例return {...state,user: {...state.user,...action.payload}};case 'ADD_ITEM':// 数组深克隆return {...state,items: [...state.items, action.item]};}
}

六、克隆方案选择指南

场景特征推荐方案原因说明
简单对象无嵌套浅克隆性能最优
需要保留特殊对象类型structuredClone()原生支持多种类型
包含函数/循环引用lodash.cloneDeep完整处理复杂情况
现代浏览器环境structuredClone()原生性能最好
需要处理DOM节点自定义实现需特殊处理DOM API
超大对象(>10MB)分块克隆避免阻塞主线程
高频克隆操作(>1000次/秒)浅克隆+immutable性能敏感场景优化

七、总结与最佳实践

  1. 理解数据本质:区分基本类型和引用类型
  2. 明确克隆需求:深克隆还是浅克隆
  3. 选择合适方案
    • 优先使用浏览器原生API:structuredClone()
    • 复杂场景使用成熟库:lodash的_.cloneDeep()
    • 特殊需求自定义实现
  4. 处理边界情况
    • 循环引用使用WeakMap
    • 特殊对象类型单独处理
    • Symbol属性不能忽略
  5. 性能考量
    • 避免在循环中深度克隆大对象
    • 考虑使用不可变数据结构
    • 超大对象使用分块处理
// 最佳实践示例
import { cloneDeep } from 'lodash-es';// 需要完全隔离的数据
const configBackup = cloneDeep(runtimeConfig);// 浏览器环境简单克隆
const stateSnapshot = structuredClone(appState);// 浅克隆配置对象
const currentSettings = { ...defaultSettings, ...userSettings };

掌握深浅克隆的区别和实现方式,是写出健壮JavaScript应用的关键技能。根据具体场景选择合适的克隆策略,可以有效避免数据污染和意外行为。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/83054.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新闻数据加载(鸿蒙App开发实战)

本案例基于ArkTS的声明式开发范式,介绍了数据请求和onTouch事件的使用。包含以下功能: 数据请求。列表下拉刷新。列表上拉加载。 网络数据请求需要权限:ohos.permission.INTERNET 一、案例效果截图 操作说明: 点击应用进入主页…

办公效率王Word批量转PDF 50 +文档一键转换保留原格式零错乱

各位办公小能手们,我跟你们说啊!在办公的时候,咱经常会碰到要把一堆Word文档转成PDF格式的情况,比如说要统一文件格式、保护文档内容或者方便分享啥的。这时候,就需要用到Word批量转换成PDF的软件啦。下面我就给你们好…

一张Billing项目的流程图

流程图 工作记录 2016-11-11 序号 工作 相关人员 1 修改Payment Posted的导出。 Claim List的页面加了导出。 Historical Job 加了Applied的显示和详细。 郝 识别引擎监控 Ps (iCDA LOG :剔除了160篇ASG_BLANK之后的结果): LOG_File 20161110.txt BLANK_CDA/ALL 45/10…

SpringAI系列4: Tool Calling 工具调用 【感觉这版本有bug】

前言:在最近发布的 Spring AI 1.0.0.M6 版本中,其中一个重大变化是 Function Calling 被废弃,被 Tool Calling 取代。Tool Calling工具调用(也称为函数调用)是AI应用中的常见模式,允许模型通过一组API或工具…

第六十三节:深度学习-模型推理与后处理

深度学习模型训练完成后,如何高效地将其部署到实际应用中并进行准确预测?这正是模型推理与后处理的核心任务。OpenCV 的 dnn 模块为此提供了强大支持,本文将深入探讨 OpenCV 在深度学习模型推理与后处理中的关键技术与实践。 第一部分:基础概念与环境搭建 1.1 核心概念解析…

uniapp开发企业微信小程序时 wx.qy.login 在uniapp中使用的时候,需要导包吗?

在 UniApp 中使用 “wx.qy.login” 不需要手动导包,但需要满足以下条件: 一、环境要求与配置 1� 企业微信环境判断 必须确保当前运行环境是企业微信客户端,通过 “uni.getSystemInfoSync().environment” 判断是否为 “wxwork”…

ONLYOFFICE文档API:更强的安全功能

在数字化办公时代,文档的安全性与隐私保护已成为企业和个人用户的核心关切。如何确保信息在存储、传输及协作过程中的安全,是开发者与IT管理者亟需解决的问题。ONLYOFFICE作为一款功能强大的开源办公套件,不仅提供了高效的文档编辑与协作体验…

Linux系统编程之共享内存

概述 在Linux系统中,共享内存也是一种高效的进程间通信机制,允许两个或多个进程共享同一块物理内存区域。通过这种方式,不同进程可以直接访问和操作相同的数据,从而避免了数据的复制。由于数据直接在内存中共享,没有额…

零知开源——STM32F407VET6驱动Flappy Bird游戏教程

简介 本教程使用STM32F407VET6零知增强板驱动3.5寸TFT触摸屏实现经典Flappy Bird游戏。通过触摸屏控制小鸟跳跃,躲避障碍物柱体,挑战最高分。项目涉及STM32底层驱动、图形库移植、触摸控制和游戏逻辑设计。 目录 简介 一、硬件准备 二、软件架构 三、…

Elasticsearch创建快照仓库报错处理

创建快照仓库报错: 根据报错提示的信息,问题可能出在 Elasticsearch 的配置中。当你尝试创建一个文件系统(fs)类型的快照仓库时,虽然已经指定了 location 参数,但 Elasticsearch 仍然报错,这通…

服务器如何配置防火墙管理端口访问?

配置服务器防火墙来管理端口访问,是保障云服务器安全的核心步骤。下面我将根据你使用的不同操作系统(Linux: Ubuntu/Debian/CentOS;Windows Server)介绍常用防火墙配置方法。 ✅ 一、Linux 防火墙配置(UFW / firewalld…

Redis最佳实践——安全与稳定性保障之连接池管理详解

Redis 在电商应用的连接池管理全面详解 一、连接池核心原理与架构 1. 连接池工作模型 #mermaid-svg-G7I3ukCljlJZAXaA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G7I3ukCljlJZAXaA .error-icon{fill:#552222;}…

打印机无法远程打印?可以本地打印,本地网络打印机设置给异地使用

很多小伙伴常有打印、远程打印的需求,特别是对于电商人、跨境电商、教师、产品经理、实验人员等群体来说掌握这项技能可谓是能够在很多场景下带来便捷,大幅提升做事效率!打印机是家庭和企业经常用到的设备,很多情况下会遇到本地可…

【Linux】进程地址空间揭秘(初步认识)

10.进程地址空间(初步认识) 文章目录 10.进程地址空间(初步认识)一、进程地址空间的实验现象解析二、进程地址空间三、虚拟内存管理补充:数据的写时拷贝(浅谈)补充:页表(…

深入探讨redis:主从复制

前言 如果某个服务器程序,只部署在一个物理服务器上就可能会面临一下问题(单点问题) 可用性问题,如果这个机器挂了,那么对应的客户端服务也相继断开性能/支持的并发量有限 所以为了解决这些问题,就要引入分布式系统&#xff0c…

MacOS安装Docker Desktop并汉化

1. 安装Docker Desktop 到Docker Desktop For Mac下载对应系统的Docker Desktop 安装包,下载后安装,没有账号需要注册,然后登陆即可。 2. 汉化 前往汉化包下载链接下载对应系统的.asar文件 然后将安装好的文件覆盖原先的文件app.asar文件…

索引的选择与Change Buffer

1. 索引选择与Change Buffer 问题引出:普通索引 vs 唯一索引 ——如何选择? 在实际业务中,如果一个字段的值天然具有唯一性(如身份证号),并且业务代码已确保无重复写入,那就存在两种选择&…

lua注意事项

感觉是lua的一大坑啊,它还不如函数内部就局部变量呢 注意函数等内部,全部给加上local得了

【多线程初阶】死锁的产生 如何避免死锁

文章目录 关于死锁一.死锁的三种情况1.一个线程,一把锁,连续多次加锁2.两个线程,两把锁3.N个线程,M把锁 --哲学家就餐问题 二.如何避免死锁死锁是如何构成的(四个必要条件)打破死锁 三.死锁小结 关于死锁 一.死锁的三种情况 1.一个线程,一把锁,连续多次加锁 -->由synchroni…

【NLP基础知识系列课程-Tokenizer的前世今生第二课】NLP 中的 Tokenizer 技术发展史

从词表到子词:Tokenizer 的“进化树” 我们常说“语言模型是理解人类语言的工具”,但事实上,模型能不能“理解”,关键要看它接收到了什么样的输入。而 Tokenizer,就是这一输入阶段的设计者。 在 NLP 的发展历程中&am…