JavaScript 深拷贝和浅拷贝 原理及实现

浅拷贝

  1. 理解拷贝之前,需要了解什么是值类型和引用类型。这一点并不难。至少现象很多人都知道:

  1. 对值类型变量再复制一个,二者互不影响。

var a = 1;
var b = a; //浅拷贝
b = 2;     //改变b的值,并不会影响到a,因为浅拷贝对基本类型而言就是值拷贝
console.log(a);
  1. 在对象类型变量中,二者会影响。(对象也叫引用数据类型),对象类型的浅拷贝则只是拷贝了地址。对象的指针地址都是指向栈内存的相同位置,修改其一,另一个也会变化

var p1 = {
    name: 'jack'
}
 
var p2 = p1;
p2.name = 'rose';
 
console.log(p1);

深拷贝

我们肯定不希望两个对象类型互相影响。所以我们要对其进行深拷贝,就是创建一个新的对象,里面的值完全复刻原来的对象,例如下面代码:

方法1.1

var p1 = {
    name: 'jack'
}
 
var p2 = {
    name: p1.name
};
p2.name = 'rose';

这是第一种方法。我们用这种方法实现深拷贝是可以的,但是如果属性太多,一个个写就会很繁琐。于是可以通过for循环遍历属性来优化,如下面代码:

方法1.2

var person={
    name:"张三",
    age:22
}

var person1={};
for( key in person){
    console.log(key);
    person1[key]=person[key];
}

console.log(person);
console.log(person1);

这种做法有一个缺点,就是:person对象的属性都是基本数据类型,如果是引用类型呢?比如数组,对象呢?直接说结论:如果属性有引用类型的数据,二者又互相影响了。

方法1.3

这里面先说一个API。Object.assign()是一种可以对非嵌套对象进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝。如果没有嵌套,是可以用这个方法的。

var p1 = {
    name: 'jack'
}
 
var p2 = {}
 
Object.assign(p2,p1);

但这个方法依然没解决属性中有引用类型的问题。先举一个对象嵌套对象的例子,如下面代码:

var person={
    name:"张三",
    age:22,
    son:{
        firstSon:"张大毛"
    }
}

var person1={};
for( key in person){
    console.log(key);
    person1[key]=person[key];
}

console.log(person);
console.log(person1);

上文代码中,person对象的属性,son,也是一个对象。如果我们利用前面的所有方法拷贝一个person1出来,对person1的son做个修改,会发现最初的person对象里的“张大毛”也被修改了。

下面两个方法是解决对象嵌套的做法

方法1.4

这个方法是实战中常用的

var p1 = {
    name: 'jack',
    age:12
}
 
var p2 = JSON.parse(JSON.stringify(p1));
 
p2.name = 'rose';

利用了将JS对象转为JSON字符串,再转回来俩步骤。相当于一个序列化和反序列化的方法

展示下此方法结果

var p1 = {
    name: '老张',
    age:12,
        son:{
            firstson:'张大儿'
        }
}
 
var p2 = JSON.parse(JSON.stringify(p1));
 
p2.name = '老李';
p2.son.firstson ='李大儿'
console.log(p2)
console.log(p1)

输出p2和p1,见下图

方法2

个人感觉面试时,也算比较常见的一道题,就是深浅拷贝,肯定是能自己实现更好。那么解决对象嵌套的问题,其实就是递归遍历,查看对象中的属性,是否还有引用类型的变量。

  1. 形参,源对象或数组

  1. 第一步先做校验:

  1. 不是对象或数组就没必要拷贝了

  1. 判断是对象还是数组,初始化结果

  1. 拿到数组和对象的每个key

  1. 核心代码就result[key]=obj[k],但注意要用递归

  1. 数组也能用对象的遍历for in形式,但实际中其他场景数组遍历还是用length

function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }
    
    // 返回结果
    return result
}

递归中,给deepClone,传的参数,其实是对象属性对应的值。假如,值是基本类型。在第一步校验的时候,就return obj了,跳出内部的递归函数,再去判断对象中下一个属性对应的值

参考资料:

1)https://blog.csdn.net/weixin_39570751/article/details/123363926

2)https://blog.csdn.net/weixin_45745641/article/details/121510217

3)https://blog.csdn.net/u011863822/article/details/121547527

视频:

1)https://www.bilibili.com/video/BV1Lu411B7VZ

2)https://www.bilibili.com/video/BV1Mh411D72h

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值