• JS深浅拷贝


    1. 概述

    深拷贝与浅拷贝在其它语言中也经常被提及到,在实际项目开发过程中也常常需要区分当前使用的到底是深拷贝还是浅拷贝,有时候在该使用深拷贝的地方,我们使用了浅拷贝,会导致深藏不露的bug。

    2. 数据类型

    在探讨深浅拷贝之前,我们先梳理一下js中的数据类型,js的数据类型分为两类:基本数据类型和引用数据类型,前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中。

    如下图所示,基本类型如number、string、boolean、Null和undefined等存储在栈内存中,而引用数据类型如Array、Object和函数等则是分别存储数据1的地址、数据2的地址和数据3的地址。
    在这里插入图片描述

    3. 深浅拷贝

    3.1 拷贝对象为基本数据类型

    js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是深拷贝。

    let a = 10;
    let b = a;
    
    a = 100;
    console.log(a)  // 100
    console.log(b)  // 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    修改其中一个变量的值,不会影响到另一个变量的值。

    3.2 拷贝对象中有引用数据类型

    浅拷贝:会在栈中开辟另一块空间,并将被拷贝对象的栈内存数据完全拷贝到该块空间中,即基本数据类型的值会被完全拷贝,而引用类型的值则是拷贝了“指向堆内存的地址”。

    深拷贝:不仅会在栈中开辟另一块空间,若被拷贝对象中有引用类型,则还会在堆内存中开辟另一块空间存储引用类型的真实数据。

    深浅拷贝的示意图如下图:
    在这里插入图片描述

    总结
    浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
    深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

    4、js中的深浅拷贝

    4.1 浅拷贝

    4.1.1 slice()

    let arr1 = [1, 42, [3, 4]]
    let arr1Copy = arr1.slice()
    arr1Copy[0] = 10      // 修改基本数据类型开辟新的内存地址
    arr1Copy[2][0] = 100  // 修改引用数据类型指向同一块内存地址
    console.log(arr1)     // [1, 42, [100, 4]]
    console.log(arr1Copy) // [10, 42, [100, 4]]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    arr1中的元素1是基本数据类型,所以arr1Copy能够改变其值,而不影响arr1的值。
    而[3, 4]是引用数据类型,arr1和arr1Copy指向同一块堆内存地址,所以这两个变量中3都变成了100。

    4.1.2 concat()

     let arr2 = ['cat', 'dog', 'pig', {'name': 'xia', 'age': 18}]
     let arr2Copy = [].concat(arr2)
     arr2Copy[2] = 'big pig'
     arr2Copy[3]['name'] = 'aa'
     console.log(arr2)
     console.log(arr2Copy)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    类似的还有…扩展运算符、Array.from、Object.assign()方法。

    4.2 深拷贝

    4.2.1 ES6的展开语法

    let a = {
      name : '张三',
       age : '18'
    }
    let b = { ...a };
    b.name = '李四';
    console.log('a:',a);   // {name: '张三', age: '18'}
    console.log('b:',b);   // {name: '李四', age: '18'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.2.2 JSON.parse(JSON.stringify(待拷贝对象))

    万能转换器 JSON.parse(JSON.stringify(obj))深拷贝已有对象,它可以深拷贝多层级的,不用担心嵌套问题。

    • JSON.stringfy() 将对象序列化成json对象
    • JSON.parse() 反序列化——将json对象反序列化成js对象
    let obj = {
      name: 'wyc'
    }
    // 将对象序列化成json对象
    let str = JSON.stringify(obj)
    // 将json对象反序列化成js对象
    let obj2 = JSON.parse(str)
    // 修改拷贝的数据
    obj2.name = 'wyc_ok'
    
    console.log(obj);   // {name: 'wyc'}
    console.log(obj2);  // {name: 'wyc_ok'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    但此拷贝的缺点,即没法拷贝内部函数

    let a = {
        name : '张三',
        age : '18',
        like(){
            console.log('喜欢唱歌、滑冰');
        }
    }
    let b =JSON.parse( JSON.stringify(a) );
    b.name = '李四';
    console.log('a:',a);
    console.log('b:',b);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    4.2.3 jQuery 中的 $.extend (添加true就是深拷贝,不添加就是浅拷贝)

    $.extend( 是否开启深拷贝, 拷贝后的值, 要拷贝的对象 )

    let origin = [[1,1], 2, 3, 4];
    let new_data = []
    
    $.extend(true,new_data,origin)
    
    new_data[0].push(5)
    
    console.log('origin:',origin);
    console.log('new_data:',new_data);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    4.2.4 手写递归的方式来实现深拷贝

    
    const origin = {
      name : '张三',
       age : '18',
       like(){
           console.log('喜欢唱歌、滑冰');
       },
       a : [[1,1], 2, 3, 4]
    }
    
    
    function extend(origin, deep){
       // deep true  启动深拷贝
       // false  浅拷贝
       let obj = {}
       // 数组对象
       if(origin instanceof Array){
           // true 数组  obj 就得是数组
           obj = []
       }
       for(let key in origin){
           let value = origin[key]
           // 确定value是不是引用型,前提是deep 是true
           obj[key] = (!!deep && typeof value === "object" && value !== null) ? extend(value, deep) : value
       }
       return obj
    }
    
    
    const new_data = extend(origin, true)
    
    new_data.a[0].push(6666)
    console.log(origin)
    console.log(new_data)
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    在这里插入图片描述

  • 相关阅读:
    【用户画像】功能实现值写入ClickHouse人群包、预估和更新分群人数,NoSQL数据库介绍
    解决uni-app小程序获取路由及路由参数
    QT中使用moveToThread让任务在子线程中进行
    C语言基本结构:顺序、选择和循环
    虚假内容检测,谣言检测,不实信息检测,事实核查;纯文本,多模态,多语言;数据集整理
    指定显卡运行python脚本
    BSPHP 未授权访问 信息泄露
    solidworks动画制作教程——装配体爆炸动画
    NeRF综述
    数据提取1
  • 原文地址:https://blog.csdn.net/wu_2004/article/details/132901489