深拷贝浅拷贝

6/24/2020 Javascript
  • 浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
  • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存修改新对象不会改到原对象

# 区别

浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制

# 没有做处理的赋值和修改

// 对象赋值
var obj1 = {
  name: 'Jioho',
  age: '18',
  language: [1, [2, 3], [4, 5]]
}
var obj2 = obj1
obj2.name = 'jioho'
obj2.language[1] = ['二', '三']
obj1.age = 22
console.log('obj1', obj1) // 获取的都是一样的对象。2个对象修改是同步的
console.log('obj2', obj2) // 获取的都是一样的对象。2个对象修改是同步的
console.log(obj1 === obj2) // true
1
2
3
4
5
6
7
8
9
10
11
12
13

# 进行浅拷贝处理

// Object.assgin() 就是浅拷贝,后续会说到
var obj1 = {
  name: 'Jioho',
  age: '18',
  language: [1, [2, 3], [4, 5]]
}
var obj2 = Object.assign({}, obj1)
obj2.name = 'jioho'
obj2.language[1] = ['二', '三']
obj1.age = 22
console.log('obj1', obj1)
console.log('obj2', obj2)
console.log(obj1 === obj2) // false

// 第二层的属性,用全等判断还是true。说明内存地址还是一样的
console.log(obj1.language === obj2.language) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 进行深拷贝

// 常用的方法是JSON.stringify + JSON.parse
var obj1 = {
  name: 'Jioho',
  age: '18',
  language: [1, [2, 3], [4, 5]]
}
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = 'jioho'
obj2.language[1] = ['二', '三']
obj1.age = 22
console.log('obj1', obj1)
console.log('obj2', obj2)
console.log(obj1 === obj2) // false
console.log(obj1.language === obj2.language) // false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

结论

操作 和原数据是否指向同一对象 第一层数据为基础数据类型 原数据包含子对象
赋值 改变使原数据一同改变 子对象一同改变
浅拷贝 第一层的原数据不变 原子对象会和新对象一起改变
深拷贝 原数据不变 原数据不变

# 浅拷贝有几种

# Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。 但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

当 object 只有一层的时候,是深拷贝

// 浅拷贝
var obj = { a: { a: 'kobe', b: 39 } }
var initalObj = Object.assign({}, obj)
initalObj.a.a = 'wade'
console.log(obj.a.a) //wade

// 深拷贝
let obj2 = {
  username: 'kobe'
}
let obj3 = Object.assign({}, obj)
obj3.username = 'wade'
console.log(obj) //{username: "kobe"}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Array.prototype.concat()

let arr = [
  1,
  3,
  {
    username: 'kobe'
  }
]
let arr2 = arr.concat()
arr2[2].username = 'wade'
console.log(arr) // 修改新对象也会影响源对象
1
2
3
4
5
6
7
8
9
10

# Array.prototype.slice()

let arr = [
  1,
  3,
  {
    username: ' kobe'
  }
]
let arr3 = arr.slice()
arr3[2].username = 'wade'
console.log(arr)
1
2
3
4
5
6
7
8
9
10

额外说明

Array 的 slice 和 concat 方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。 原数组的元素会按照下述规则拷贝:

  • 如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

  • 对于字符串数字布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组

# 深拷贝

# JSON.parse(JSON.stringify())

  • 优点:简单易用
  • 缺点:无法处理函数
  • 原理: 用 JSON.stringify 将对象转成 JSON 字符串,再用 JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

# 手写递归方法

//定义检测数据类型的功能函数
function checkedType(target) {
  return Object.prototype.toString.call(target).slice(8, -1)
}
//实现深度克隆---对象/数组
function clone(target) {
  //判断拷贝的数据类型
  //初始化变量result 成为最终克隆的数据
  let result,
    targetType = checkedType(target)
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }
  //遍历目标数据
  for (let i in target) {
    //获取遍历数据结构的每一项值。
    let value = target[i]
    //判断目标结构里的每一值是否存在对象/数组
    if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
      //对象/数组里嵌套了对象/数组
      //继续遍历获取到value值
      result[i] = clone(value)
    } else {
      //获取到value值是基本的数据类型或者是函数。
      result[i] = value
    }
  }
  return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 借助第三方函数库

函数库 lodash

var _ = require('lodash')
var obj1 = {
  a: 1,
  b: { f: { g: 1 } },
  c: [1, 2, 3]
}
var obj2 = _.cloneDeep(obj1)
console.log(obj1.b.f === obj2.b.f) // false
1
2
3
4
5
6
7
8
Last Updated: 5/9/2021, 10:48:13 PM