• js:深拷贝与浅拷贝的区别,如何实现深拷贝


    背景

    JavaScript存在两大数据类型:

    • 基本数据类型(String, Number, Boolean, null, undefined, Symbol, BigInt)
    • 引用数据类型 (Object, Array, Data, Math, RegExp)

    其中

    • 基本类型数据存储在栈内存
    • 引用数据类型,数据保存在堆内存中,引用数据类型的变量存储在栈中,是一个指向堆内存中实际对象的引用。

    栈区:会自动分配内存空间,自动释放,占用固定大小空间,存放基本类型。
    堆区:会动态分配内存空间,大小不固定且不会自动释放,存放引用类型。 基本数据类型(栈区)

    补充:内存中的栈区(stack)和堆区(heap)

    “基本数据类型”在内存中占有固定大小的空间,通过按值访问。“引用数据类型”在内存中大小不固定,所以其在栈中只存放堆内存地址(内存地址是大小固定的),指向的却是堆中的对象,所以它是按引用访问的。假如需要获取“引用数据类型”中的变量,需要先访问栈内存中的地址,然后根据该地址才能获取到相对应的值。

    浅拷贝和深拷贝

    对于基本数据类型而言并没有 “深浅拷贝” 的区别,对于基本数据类型而言并没有 “深浅拷贝” 的区别
    概括:当你在目标对象中修改一个值的时候,看对原对象是否有影响。如果有影响那就是浅拷贝,如果没有那就深拷贝。

    浅拷贝

    浅拷贝会在栈中开辟一个新的内存空间,将原对象一级中的“基本数据类型”复制一份到新的内存空间,所以相互不影响。当对象中有“引用类型”时,它只能拷贝“引用类型”在堆内存中的地址,所以赋值后会影响原对象的值。

    存在浅拷贝现象的有:

    • Object.assign()
    • ES6中的扩展运算符
    • Array.prototype.slice()和Array.prototype.concat()

    1、Object.assign(target, …sources)

    使用说明:Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    //如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性
    const object1 = {
      a: 1,
      b: 2,
      c: 3
    };
    const object2 = Object.assign({c: 4, d: 5}, object1);
    console.log(object1)  // { a: 1, b: 2, c: 3 }
    console.log(object2)  // { c: 3, d: 5, a: 1, b: 2 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    浅拷贝说明:

    //当定义的对象只有基本类型时,该方法就是深拷贝。
    let a = {
        name: 'sweet',
        age: 18
    };
    let b = Object.assign({}, a);
    b.name = "哈哈";
    console.log(a);   // a = {name: 'sweet', age: 18};
    console.log(b);   // b = {name: '哈哈', age: 18};
    
    //当定义的对象中有引用类型的时,该方法就是浅拷贝。
    let a = {
        name: 'sweet',
        age: 18,
        eat:{
            type: '苹果',
            price: 18,
        }
    };
    let b = Object.assign({}, a);
    b.name = "哈哈";
    b.eat.type = "西瓜";
    b.eat.price = 30;
    console.log(a);  // a = {name: 'sweet', age: 18, eat:{ type: '西瓜', price: '30'}};
    console.log(b);  // b = {name: '哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2、ES6中的扩展运算符

    //我们通过使用扩展运算符(...)也能实现深拷贝。该方法和上述方法一样只能用于深拷贝第一层的值,当拷贝第二层的值时,仍是引用同一个内存地址。
    //深拷贝
    let a = {
        name: 'sweet',
        age: 18
    };
    let b = {...a};
    b.name = "哈哈";
    console.log(a);   // a = {name: 'sweet', age: 18};
    console.log(b);   // b = {name: '哈哈', age: 18};
    //浅拷贝
    let a = {
        name: 'sweet',
        age: 18,
        eat:{
            type: '苹果',
            price: 18,
        }
    };
    let b = {...a};
    b.name = "哈哈";
    b.eat.type = "西瓜";
    b.eat.price = 30;
    console.log(a);  // a = {name: 'sweet', age: 18, eat:{ type: '西瓜', price: '30'}};
    console.log(b);  // b = {name: '哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3、数组中的方法:Array.prototype.slice() 和 Array.prototype.concat()

    同上,只能用于深拷贝第一层的值,当拷贝第二层的值时仍是引用同一个内存地址。

    深拷贝

    深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
    常见的深拷贝方式有:

    • _.cloneDeep()
    • jQuery.extend()
    • JSON.stringify()
    • 手写循环递归

    1、_.cloneDeep()

    使用第三方函数库 lodash

    const _ = require('lodash');
    const obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    const obj2 = _.cloneDeep(obj1);
    console.log(obj1.b.f === obj2.b.f);// false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2、jQuery.extend()

    const $ = require('jquery');
    const obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    const obj2 = $.extend(true, {}, obj1);
    console.log(obj1.b.f === obj2.b.f); // false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、JSON.parse(JSON.stringfy(obj))

    对象深拷贝

    //该方法可以实现深拷贝。 但是需要注意
    1:会忽略undefined,Symbol,函数。
    2:在处理new Date() 会出错
    3:循环引用会出错
    4:不能处理正则,拷贝的是一个空对象
    5:继承的属性会丢失
    一句话概括:可以转成 JSON 格式的对象才能使用这种方法。
    
    let a = {
        name: 'sweet',
        age: 18,
        fn: function(){},
        from: undefined,
        to: Symbol('深圳'),
        nums: /'g'/,
        eat:{
            type: '苹果',
            price: 18,
        }
    };
    let b = JSON.parse(JSON.stringify(a));
    b.name = "哈哈";
    b.eat.type = "西瓜";
    b.eat.price = 30;
    console.log(a);  // a = {name: 'sweet', age: 18, nums: /'g'/, fn: function(){}, eat:{ type: '苹果', price: '18'}};
    console.log(b);  // b = {name: '哈哈', age: 18, nums: {}, eat:{ type: '西瓜', price: '30'}};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    数组深拷贝

    let arr = [1, 2, [1]];
    let newArr = JSON.parse(JSON.stringify(arr));
    newArr[2][0] = 10;
    console.log(arr, newArr); // [1, 2, [1]] [1, 2, [10]]
    
    let arr1 = [1, 2, [1]];
    let newArr1 = JSON.parse(JSON.stringify(arr1));
    newArr1[2][0] = 10;
    console.log(arr1, newArr1); // [1, 2, [1]] [1, 2, [10]]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4、递归函数

    function deepClone(obj, hash = new WeakMap()) {
      if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
      if (obj instanceof Date) return new Date(obj);
      if (obj instanceof RegExp) return new RegExp(obj);
      // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
      if (typeof obj !== "object") return obj;
      // 是对象的话就要进行深拷贝
      if (hash.get(obj)) return hash.get(obj);
      let cloneObj = new obj.constructor();
      // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
      hash.set(obj, cloneObj);
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          // 实现一个递归拷贝
          cloneObj[key] = deepClone(obj[key], hash);
        }
      }
      return cloneObj;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    总结

    • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
    • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
  • 相关阅读:
    【正点原子STM32连载】第三十九章 DS18B20数字温度传感器实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
    使用vscode开发esp32
    Web Vue VI
    【Leetcode】149.直线上最多的点数(Hard)
    (数据科学学习手札157)pandas新增case_when方法
    【Phoenix】在Kerberos认证下使用JDBC连接Phoenix 和 Phoenix各数据类型测试表创建
    听说JetBrains要涨价,我赶紧把Goland续费到2025年!!!
    AI无处不在,科技改变生活:开放原子全球开源峰会参会感悟
    Euclidean Distance Transform - EDT
    剑指 Offer 06. 从尾到头打印链表
  • 原文地址:https://blog.csdn.net/m0_51055743/article/details/126351862