详解 JS 深拷贝与浅拷贝问题

lxf2023-05-18 01:34:35

1.什么是深拷贝和浅拷贝 ?

在面试时经常会碰到面试官问: 什么是深拷贝和浅拷贝,请举例说明?如何区分深拷贝与浅拷贝,简单来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝;

我们先看两个简单的案例

案例1:

详解 JS 深拷贝与浅拷贝问题

案例2:

详解 JS 深拷贝与浅拷贝问题

照常规的思维,o1应该和a1是一样的,不会因为另外一个值的改变而发生改变,而这里的o1却随着o2的改变而改变了。同样是变量,为什么表示的不一样呢? 为了更好的理解js的深拷贝和浅拷贝。我们先来理解一些js基本的概念:目前JavaScript有五种基本数据类型(也就是简单数据类型),它们分别是:NumberBooleanstringnullundefined。还有一种复杂的数据类型(也叫引用类型),就是对象;常见的引用类型有:object对象、array数据、function函数等......

2.基本数据类型和引用数据类型

ECMAScript变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。 基本类型指的是哪些保存在内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指的是哪些保存在堆内存中的对象,意思是变量中保存的实际上就只是一个指针,这个指针指向内存中的另一个位置,而该位置保存对象。

哪什么是堆?什么又是栈?

3.JS中的堆内存和栈内存

计算器语言有一个处理的过程,写的代码会进行解释或编译执行,这个过程是在内存中,内存的使用和 分配,涉及到堆和栈,任何语言都有堆和栈 ,堆和栈都存放在内存中。

详解 JS 深拷贝与浅拷贝问题

3.1 栈内存

栈:JavaScript的基本数据类型就五种:undefinednullstringnumberBoolean。它们都是直接按值存储在栈内存中,每种类型的数据占用的内存空间的大小是确定的。栈由系统自动分配,比如:声明在函数中的一个局部变量var a;,系统自动在栈中为a开辟空间,只要栈的剩余空间大于所要的申请空间。系统将为程序提供内存,否则将报异常提示栈溢出。

3.2 堆内存

堆:JavaScript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function)等 …, 它们是通过拷贝和new出来的,这样的数据存储于堆中;其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。

详解 JS 深拷贝与浅拷贝问题

4.引用类型如何实现深拷贝

4.1 Array(数组)

对于数组来讲,我们可以使用slice()concat()来解决上面的问题。

4.1.1 .slice()

详解 JS 深拷贝与浅拷贝问题

4.1.2 .concat()

详解 JS 深拷贝与浅拷贝问题

4.1.3 多维数组也可以吗 ?

我们把arr1改成二维数组再来看看。

var arr1 = ['a', 'b', ['c', 'd']],
    arr2 = arr1.concat();
    arr2[2][1] = 100;

  console.log(arr1); //['a', 'b', ['c', 100]]
  console.log(arr2); //['a', 'b', ['c', 100]]

咦, arr2又改变了arr1, 看来slice()concat() 只能实现一维数组的深拷贝。

4.2 object(对象)

利用对象的深拷贝实现原理定义一个新的对象,遍历源对象的属性并赋给新对象的属性。

 const obj = {
      name: '果果',
      age: 18
    }

    const obj2 = new Object();

    obj2.name = obj.name;
    obj2.age = obj.age
    obj.name = '娇娇';

    console.log(obj); //Object {name: "'娇娇'", age: 18}
    console.log(obj2); //Object {name: "'果果'", age: 18}
4.2.1 JSON.parse(JSON.stringify(obj))
 var obj = {
      name: '果果',
      age: 18
    }

var obj2 = JSON.parse(JSON.stringify(obj));
    obj.name = '娇娇';

    console.log(obj) // {name: "娇娇", age: 18}
    console.log(obj2) // {name: "果果", age: 18}
4.2.2 JSON.parse(JSON.stringify(obj))深拷贝的坑

利用JSON.stringifyjs对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;

注意以下几点:

  1. 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串
  2. 如果obj里有RegExpError对象,则序列化的结果将只得到空对象。
  3. 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
  4. 如果obj里有NaNInfinity-Infinity,则序列化的结果会变成null
  5. JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor
  6. 如果对象中存在循环引用的情况也无法正确实现深拷贝。
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!