当前位置:首页 > 技术 > 正文内容

JavaScript 浅拷贝与深拷贝的实现原理及方案

访客 技术 2026年7月2日 3

在 JavaScript 中,处理对象和数组的复制时,理解浅拷贝(Shallow Copy)与深拷贝(Deep Copy)的区别至关重要。两者的核心差异在于对引用类型属性的处理方式。

1. 浅拷贝的机制与常见实现

浅拷贝会创建一个新对象,并将原始对象的属性值精确拷贝一份。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型(如对象或数组),则拷贝的是内存地址。这意味着新旧对象会共享同一块内存空间,修改其中一个对象的引用属性会影响另一个。

1.1 使用 Object.assign

Object.assign 是 ES6 提供的常用方法,它可以将一个或多个源对象的可枚举属性复制到目标对象。

const sourceModel = { id: 1, info: { name: "Tech" } };
const clonedModel = Object.assign({}, sourceModel);

// 注意:它不会拷贝原型链上的属性,也不会拷贝不可枚举属性。

1.2 扩展运算符 (...)

对象的扩展运算符提供了更简洁的语法来实现浅拷贝,其表现与 Object.assign 基本一致。

const original = { a: 1, b: { c: 2 } };
const copy = { ...original };

1.3 数组的浅拷贝方法

对于数组类型,可以通过以下方法快速生成浅拷贝副本:

  • Array.prototype.slice():通过 arr.slice() 不传参数获取新数组。
  • Array.prototype.concat():通过 [].concat(arr) 连接空数组。

1.4 使用属性描述符克隆

如果需要保留对象的原型并获取完整的属性描述符,可以使用 Object.getOwnPropertyDescriptors 配合 Object.create

const smartClone = (origin) => Object.create(
  Object.getPrototypeOf(origin),
  Object.getOwnPropertyDescriptors(origin)
);

2. 深拷贝的实现方案

深拷贝会递归地复制对象及其所有层级的嵌套对象。拷贝完成后,新对象与原对象在内存中完全独立。

2.1 JSON 序列化(快捷但有限制)

利用 JSON.stringify 将对象转为字符串,再用 JSON.parse 还原。这是一种简单高效的方法,但存在以下局限性:

  • 会忽略 undefinedSymbol 和函数。
  • RegExpError 对象会被转为空对象。
  • 无法处理循环引用(Circular Reference)。
  • SetMap 等特殊数据结构会被转换成空对象 {}
const complexData = {
  active: true,
  tags: [1, 2],
  meta: { author: "Admin" }
};
const deepResult = JSON.parse(JSON.stringify(complexData));

2.2 递归实现(通用方案)

为了处理复杂的数据结构、循环引用以及特殊的内建对象(如 Map 和 Set),通常需要编写一个递归函数。通过 WeakMap 来存储已拷贝的对象引用,可以有效解决循环引用导致的栈溢出问题。

function performDeepClone(target, cache = new WeakMap()) {
    // 处理基本类型及 null
    if (target === null || typeof target !== 'object') {
        return target;
    }

    // 防止循环引用
    if (cache.has(target)) {
        return cache.get(target);
    }

    let result;

    // 处理特殊引用类型
    if (Array.isArray(target)) {
        result = [];
    } else if (target instanceof Set) {
        result = new Set();
        target.forEach(val => result.add(performDeepClone(val, cache)));
        return result;
    } else if (target instanceof Map) {
        result = new Map();
        target.forEach((val, key) => result.set(key, performDeepClone(val, cache)));
        return result;
    } else {
        result = {};
    }

    // 记录当前对象缓存
    cache.set(target, result);

    // 递归处理子属性
    Object.keys(target).forEach(key => {
        result[key] = performDeepClone(target[key], cache);
    });

    return result;
}

// 示例:处理循环引用
const nodeA = { name: 'Node A' };
nodeA.self = nodeA; 

const clonedNode = performDeepClone(nodeA);
console.log(clonedNode !== nodeA); // true
console.log(clonedNode.self === clonedNode); // true

在生产环境中,如果对性能和边界情况有极高要求,建议使用成熟的第三方库如 lodash.cloneDeep,或者在现代浏览器中使用原生 API structuredClone()

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。