浅拷贝和深拷贝

1. 核心概念:从内存讲起

要理解拷贝,首先要明白 JavaScript 如何存储值。

  • 基本类型(Primitive Types)String, Number, Boolean, null, undefined, Symbol, BigInt

    • 这些值直接存储在栈内存 中。变量赋值或传递时,会创建一个值的完全独立的副本。操作一个不会影响另一个。
    javascript 复制代码
    let a = 10;
    let b = a; // b 是 a 的一个独立副本
    b = 20;
    console.log(a); // 10 (未受影响)
  • 引用类型(Reference Types)Object, Array, Function, Date, Map, Set 等。

    • 这些值存储在堆内存 中。变量名实际存储的是一个指向堆内存地址的指针(引用)
    • 当你进行 let obj2 = obj1 这样的操作时,你拷贝的只是这个指针 ,而不是堆内存中的对象本身。因此,obj1obj2 指向同一个对象
    javascript 复制代码
    let obj1 = { name: 'Alice' };
    let obj2 = obj1; // 只拷贝了引用地址!
    obj2.name = 'Bob';
    console.log(obj1.name); // 'Bob' (原对象被修改了!)
    • 正是引用类型的这种特性,使得"拷贝"变得复杂,并衍生出浅拷贝和深拷贝的概念。

2. 浅拷贝 (Shallow Copy)

定义

创建一个新对象,新对象拥有原始对象第一层属性 的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址(即复制引用,而不是复制引用的对象本身)。

简单说:只拷贝了第一层,如果原对象包含嵌套对象,那么嵌套对象仍然是共享的。

前端实现方式

  1. 展开运算符 (...)

    javascript 复制代码
        const oldList = [1, 2, 3, { name: 'tom' }]
        const newList = [...oldList]
        newList[3].age = 18
        console.log('oldList ====>', oldList, 'newList ====>', newList); 
        // [1, 2,3,{ "name": "tom","age": 18},4]
  2. Object.assign()

    javascript 复制代码
        const oldList = [1, 2, 3, { name: 'tom' }]
        const newList = Object.assign([],oldList)
        newList[3].age = 18
        console.log('oldList ====>', oldList, 'newList ====>', newList); 
        // [1, 2,3,{ "name": "tom","age": 18},4]

3. 深拷贝 (Deep Copy)

定义

创建一个新对象,并递归地拷贝原始对象中的所有属性及其嵌套的属性。直到所有层级的所有属性都被拷贝完毕,新对象和原对象完全脱离关系,互不影响。

简单说:从里到外,全部拷贝一份新的。

前端实现方式及优劣

  1. JSON.parse(JSON.stringify(object)) (最常用但有缺陷)

    javascript 复制代码
    const original = { name: 'Leo', hobbies: ['coding', 'reading'] };
    const deepCopy = JSON.parse(JSON.stringify(original));
    • 优点:简单粗暴,对于大多数常规数据结构非常有效。
    • 致命局限性
      • 丢失特殊对象 :会丢弃 undefinedFunctionSymbol 作为值的属性。
      • 无法处理循环引用:对象内部或对象之间存在循环引用时会报错。
      • 损坏对象类型Date 对象会被转换成 ISO 字符串格式,Set, Map, RegExp 等会变成空对象 {}
      • 性能:处理非常大的对象时,序列化和反序列化过程可能较慢。
  2. 使用第三方库 (lodash.clonedeep) (生产环境推荐)

  3. 手动实现递归函数 (面试常见,理解原理)

    javascript 复制代码
    function deepClone(obj, hash = new WeakMap()) {
         // 处理基本类型和null/undefined
         if (obj === null || typeof obj !== 'object') return obj;
         // 处理Date对象
         if (obj instanceof Date) return new Date(obj);
         // 处理Array对象
         if (obj instanceof Array) return obj.map(item => deepClone(item, hash));
    
         // 处理循环引用
         if (hash.has(obj)) return hash.get(obj);
    
         // 处理普通Object对象
         const clonedObj = Object.create(Object.getPrototypeOf(obj));
         hash.set(obj, clonedObj); // 缓存克隆对象,用于循环引用
    
         for (let key in obj) {
             if (obj.hasOwnProperty(key)) {
                 clonedObj[key] = deepClone(obj[key], hash);
             }
         }
         return clonedObj;
     }
    
     const oldList = [1, 2, 3, { name: 'tom' }]
     const newList = deepClone(oldList)
    
     newList[3].age = 18
     console.log('oldList ====>', oldList, 'newList ====>', newList);

    结果:

相关推荐
在掘金801102 小时前
pm2 程序 windows开机启动管理设置
前端
徐_三岁2 小时前
深入理解 svh、lvh、dvh—— 移动端视口高度解决方案
前端·css
昔人'2 小时前
css`min()` 、`max()`、 `clamp()`
前端·css
鹏多多2 小时前
Vue项目i18n国际化多语言切换方案实践
前端·javascript·vue.js
一只小风华~2 小时前
Vue: 侦听器(Watch)
前端·javascript·vue.js
JarvanMo2 小时前
Flutter Debug模式:每个开发者都需要的秘密武器(但大多数人用错了)
前端
GDAL2 小时前
Knockout.js 备忘录模块详解
javascript·knockout
玲小珑2 小时前
LangChain.js 完全开发手册(八)Agent 智能代理系统开发
前端·langchain·ai编程
蓝胖子的多啦A梦3 小时前
【前端】VUE+Element UI项目 页面自适应横屏、竖屏、大屏、PDA及手机等适配方案
前端·javascript·elementui·html·前端页面适配