【JavaScript】深拷贝和浅拷贝

155 阅读3分钟

一、基础了解

1.1 堆和栈

栈和堆都是放在堆内存中的,而在堆内存中,JS把其分为栈结构和堆结构,这里常被误认为是堆内存和栈内存,但是我们可以把它简称为栈和堆。

  • 栈(stack)
    • 特点:
      • 系统会自动分配内存空间,会自动释放,存放基本类型和引用数据类型的变量
      • 所有在函数/方法中定义的变量都是放在栈内存中,随着函数/方法的执行结束,这个函数/方法的内存栈也自然销毁。
    • 优点:存取速度比堆快。
    • 缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
  • 堆(heap) :
    • 特点:
      • 动态分配的内存,大小不定也不会自动释放,存放引用类型的对象,指那些可能由多个值构成的对象,保存在堆内存中。
      • 堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用。
    • 优点:动态的分配内存大小,生存期不必事先告诉编译器
    • 缺点:要在运行时动态分配内存,存取速度较慢。

1.2 JS的数据类型的 存储方式

  • 基本数据类型存储方式

    基本数据类型被保存在栈结构中,在栈结构中会有全局执行环境(也称全局执行上下文),在全局作用域中的基本数据类型就是被保存全局执行环境中的。

  • 复杂数据类型(引用数据类型)存储方式

    复杂数据类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。

    复杂数据类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。

二、浅拷贝和深拷贝

2.1 浅拷贝

  • 浅拷贝是什么:浅拷贝是创建一个对象,这个对象有原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址

  • 示例:

    基础类型:

    image.png

    基础类型 - 修改拷贝的变量值,不影响原始对象:

    image.png

    引用类型:

    image.png

    引用类型 - 修改拷贝的变量值,影响原始对象:

    image.png

2.2 深拷贝

  • 深拷贝是什么:深拷贝是创建一个对象,这个对象是将原始对象从内存中完整的拷贝一份出来,从堆中开辟一个新的区域存放新对象,彼此完全独立。

  • 示例:

    基础类型:

    image.png

    基础类型 - 修改拷贝的变量值,不影响原始对象:

    image.png

    引用类型:

    image.png

    引用类型 - 修改拷贝的变量值,不影响原始对象:

    image.png

2.2.1 深拷贝的方式

  • JSON.parse(JSON.stringfy(obj))

    • 缺点:

      • 时间对象会变成字符串
      • RegExp、Error对象,则序列化的结果将只得到空对象。
      • 函数,undefined,则序列化的结果会把函数, undefined丢失。
      • NaN、Infinity和-Infinity,则序列化的结果会变成null。
      • 会丢弃对象的constructor。
      • 如果对象中存在循环引用的情况也无法正确实现深拷贝。

      image.png

  • loadhash库

    • 安装:npm install loadsh
    • 引用:const { cloneDeep } = require('lodash')
    • 使用:let obj1 = {}; let obj2 = cloneDeep(obj1)
  • 自定义递归方法

    示例:

    image.png

2.3 浅拷贝和深拷贝的区别

  • 变量值是基本类型:
    • 浅拷贝拷贝的是基本类型的值
    • 深拷贝拷贝的是基本类型的值
  • 变量值是引用类型:
    • 浅拷贝拷贝的是内存地址,修改原始对象或拷贝对象的值会影响到彼此
    • 深拷贝拷贝的不是内存地址,而是在堆中开辟一个新的区域存放新对象,彼此完全地理