JavaScript 深拷贝与浅拷贝

350 阅读3分钟

深拷贝与浅拷贝

什么是深拷贝与浅拷贝?

  • 古语云:古语云:甲乙学于师,甲学善外,乙学善内。师验其效,甲学如师,是谓浅学;乙学不止于师而独立,是谓深学。
  • 简单来说,B去赋值A数据,A改变了,B也跟着改变,就叫浅拷贝;A改变了,B并不跟着改变,就叫深拷贝。

1 拷贝的对象

一般来讲深浅拷贝是针对于引用数据类型,即对象、数组之类。

数据分为基本数据类型和引用用数据类型两种,基本数据类型数据存储在栈内存中;引用数据类型数据存储在堆内存中,只在占内存中存放开辟在堆内存中的起始地址。

2 区别

  • 为什么要使用深拷贝?
    • 希望在改变新的数组(对象)的时候,不改变原数组(对象)
  • 关于拷贝层级
    • 浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用;
    • 深拷贝是拷贝多层,每一级别的数据都会拷贝出来;

2.1 浅拷贝

对与浅拷贝,若被拷贝对象是基本数据类型,拷贝就相当于直接赋值;若被拷贝对象除基本数据类型之外,还有若干层对象,那么只能拷贝其引用地址,对象改变拷贝的数值也改变。

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存(分支)。
    1. 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
    2. 如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

2.2 浅拷贝的实现方式

  1. Object.assign()

    Object.assign(target, ...sources)
    //target:目标对象
    //sources:任意多个源对象。
    //返回值:目标对象会被返回。
    
    • 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
    • 当object只有一层的时候,是深拷贝
  2. Array.prototype.concat()

    • 修改新对象会改到原对象
  3. Array.prototype.slice()

    • 修改新对象会改到原对象
    • Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组

2.3 深拷贝

  • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)
    1. 拷贝第一层级的对象属性或数组元素
    2. 递归拷贝所有层级的对象属性和数组元素
    3. 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

2.4 深拷贝的实现方式

  1. JSON.parse(JSON.stringify())
    • 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
    • 可以实现数组或对象深拷贝,但不能处理函数。因为JSON.stringify() 方法不能接收函数。
    • 那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝
  2. 手写递归方法
    • 遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
  3. 函数库lodash
    • 该函数库提供_.cloneDeep用来做 Deep Copy。
  4. Object.create()方法
    • 直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。