JavaScript深拷贝和浅拷贝

深拷⻉ 浅拷⻉
⾸先说下堆栈,基本数据类型与引⽤数据类型,深拷⻉与浅拷⻉与此相关。

⼀、基本数据类型 和 引⽤数据类型

1.变量类型分为两类: 基本数据类型: number , string , boolean , null , undefined , symbol
引⽤数据类型:统称为 Object 类型,细分的话,有: Object , Array , Date , Functi
on 等。
2.存储⽅式:
a.基本数据类型保存在 栈 内存,形式如下:栈内存中分别存储着变量的标识符以及变量的 值。例:

let a = 'A';

在这里插入图片描述

b.引⽤数据类型保存在 栈 内存,形式如下:名存在栈内存中,值存在于堆内存中,但是栈内 存会提供⼀个引⽤的地址指向堆内存中的值。
例:

let a = {name:A};

在这里插入图片描述

3.不同类型的复制⽅式:
a.基本数据类型:
let a = 1;
当你let b = a 时,栈内存会新开辟⼀个内存,例如这样:
在这里插入图片描述

此时改变 a 变量的值,并不会影响 b 的值。
b.引⽤数据类型:

let a = {name: 'A',age: 10};
let b = a;
a.age = 20;

// 此时改变a的值,会改变b的值,此时内存中是这样的:
在这里插入图片描述

⼆、浅拷⻉ 和 深拷⻉

浅拷⻉ :创建⼀个新的数据,这个数据有着原始数据属性值的⼀份精确拷⻉。如果属性是基 本类型,拷⻉的就是基本类型的值,如果属性是引⽤类型,拷⻉的就是内存地址,所以如果其 中⼀个数据改变了这个地址,就会影响到另⼀个数据。
可以说 浅拷⻉ 只解决了数据第⼀层的问题,拷⻉第⼀层的基本类型值,以及第⼀层的引⽤类型地址

深拷⻉ :深拷⻉会拷⻉所有的属性,并拷⻉属性指向的动态分配的内存。当对象和它所引⽤ 的对象⼀起拷⻉时即发⽣深拷⻉。深拷⻉相⽐于浅拷⻉速度较慢并且花销较⼤。在堆中重新分 配内存,拥有不同的地址,且值是⼀样的,复制后的对象与原来的对象是完全隔离,互不影响。

三、实现深拷⻉
1.数据只有⼀层的时候: Object.assign() ⽅法可以把任意多个的源对象⾃身的可枚举属性 拷⻉给⽬标对象,然后返回⽬标对象。但是 Object.assign() 进⾏的是浅拷⻉,拷⻉的是 对象的属性的引⽤,⽽不是对象本身。当数据只有⼀层的时候,是深拷⻉。
相同的还有数组⽅法 slice 、 concat ,他们都为浅拷⻉,当数据只有⼀层的时候,可实现深 拷⻉的效果
例:

let a=[1,2,3,4,{age: 1}];
let b=Object.assign([],a);
a[0]=2;
a[4].age=2;
console.log(a,b);
// (5) [2, 2, 3, 4, {…}]
// 0: 2
// 1: 2
// 2: 3
// 3: 4
// 4: {age: 2}
// length: 5
// __proto__: Array(0)
// (5) [1, 2, 3, 4, {…}]
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// 4: {age: 2}
// length: 5
// __proto__: Array(0)

2.简单的 递归 函数:

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断obj⼦元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
  1. JSON.parse(JSON.stringify()) ⽤JSON.stringify将对象转成JSON字符串,再⽤
    JSON.parse()把字符串解析成对象,⼀去⼀来,新的对象产⽣了,⽽且对象会开辟新的栈, 实现深拷⻉。
let a=[1,2,3,4,{age: 1}];
let b=JSON.parse(JSON.stringify(a));
a[0]=2;
a[4].age=2;
console.log(a,b);
// (5) [2, 2, 3, 4, {…}]
// 0: 2
// 1: 2
// 2: 3
// 3: 4
// 4: {age: 2}
// length: 5
// __proto__: Array(0)
// (5) [1, 2, 3, 4, {…}]
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// 4: {age: 1}
// length: 5
// __proto__: Array(0)

该⽅法有⼏个缺陷:
1、会忽略 undefined 、 symbol 和 函数 ,例:

let obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A') }
let obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

2、对象循环引⽤时,会报错。
例:

  let obj = {
      name1: 'A',
      name2: {
          name3: 'B'
      },
  }
  obj.name1 = obj.name2;
  obj.name2.name3 = obj.name1;
  let obj2 = JSON.parse(JSON.stringify(obj));
  console.log(obj2); // Converting circular structure to JSON

3、new Date,转换结果不正确
4、正则会被忽略

MDN的解释,JSON.stringify() 将值转换为相应的JSON格式:
转换值如果有 toJSON() ⽅法,该⽅法定义什么值将被序列化。
⾮数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
布尔值、数字、字符串的包装对象在序列化过程中会⾃动转换成对应的原始值。
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在⾮数组对象的 属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时, 会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined).
对包含循环引⽤的对象(对象之间相互引⽤,形成⽆限循环)执⾏此⽅法,会抛出错误。
所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了 它们。
Date ⽇期调⽤了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会 被当做字符串处理。
NaN 和 Infinity 格式的数值及 null 都会被当做 null。
其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
MDN地址:developer.mozilla.org/zh-CN/docs/…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值