深拷贝(深克隆)

深拷贝(深克隆)

之前写过一篇关于浅拷贝与深拷贝的文章,文章中提到浅拷贝和深拷贝主要是针对引用数据类型(对象、数组、函数)而言的,因为对于基础数据类型(string、number、boolean、null、undefined),不存在浅拷贝这一说,只要复制一份,就是一次深拷贝,即通过复制生成的值与原始值之间没有联系。那么深拷贝的实现方式有哪些呢?

1.JSON.stringify()、JSON.parse()

最简单粗暴的方法,先通过JSON.stringify()将数据序列化成JSON字符串,然后在使用JSON.parse()方法将JSON字符串反序列化成js对象。

var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
}
var obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 'hello'
obj1.a // [1, 2, 3]
obj2.a // 'hello'

使用JSON.stringify()、JSON.parse()方法将obj1深拷贝一份赋值给obj2,当改变obj2中a的值时,obj1中a的值并没有随着改变,说明obj1和obj2是两个完全独立的对象,而不是指向同一对象的两个索引。

使用JSON.stringify()、JSON.parse()方法就可以通吃深拷贝了吗?答案是否定的,使用JSON.stringify()、JSON.parse()方法实现深拷贝会存在以下问题:

1.1、存在循环引用时会报错

var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈'
}
obj1.self = obj1

var obj2 = JSON.parse(JSON.stringify(obj1))

在这里插入图片描述
上面obj1的self属性值指向obj1,因此obj1是一个存在循环引用的对象,因此在使用JSON.stringify()、JSON.parse()方法进行深拷贝时,就会出现上图中的报错。

1.2、存在函数时无法深复制成功

var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
	fn: function() {
		console.log('fn')
	}
}
var obj2 = JSON.parse(JSON.stringify(obj1))

在这里插入图片描述
上面obj1的fn属性值是一个函数,执行深复制后打印obj2值,会发现obj2中没有fn属性。

1.3、存在正则表达式时无法复制成功

var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
	reg: /^(a-z)*$/
}

var obj2 = JSON.parse(JSON.stringify(obj1))

在这里插入图片描述
上面obj1的reg属性值是一个正则表达式,执行深复制后打印obj2值,会发现obj2中reg属性值是一个空对象。

1.4、存在时间值时无法复制成功

var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
	time: new Date()
}

var obj2 = JSON.parse(JSON.stringify(obj1))

在这里插入图片描述
上面obj1的time属性值是一个时间对象,执行深复制后打印obj2值,会发现obj2中time属性值是一个时间字符串。

1.5、存在symbol值时无法复制成功

var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
	sym: Symbol()
}

var obj2 = JSON.parse(JSON.stringify(obj1))

在这里插入图片描述
上面obj1的sym属性值是一个Symbol类型值,执行深复制后打印obj2值,会发现obj2中没有sym属性。

1.6、存在构造函数或构造函数生成的实例时,复制之后的构造函数或构造函数生成实例的constructor会丢失

function Person(name){
	this.name = name
}
var obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
	instance: new Person('Ryan')
}

var obj2 = JSON.parse(JSON.stringify(obj1))

在这里插入图片描述
上面obj1的instance属性值是Person的一个实例,执行深复制后打印obj2值,会发现obj2的instance属性值的构造函数constructor指向Object构造函数而不是Person构造函数。

2.自己实现一个功能比较完整的深拷贝

深拷贝中解决循环引用时调用栈超出最大值的思路就是当拷贝后新生成对象中存在引用自身时,确保该值的指向为新生成的对象,而不是原对象,从而切断新生成对象与原对象之间的联系,保证循环引用正确,如下图所示:

在这里插入图片描述

解决循环引用常见的有两种方法:

1、使用weakMap生成hash表来处理循环引用,即在weakMap中以原对象为key,新生成的对象为value储存起来,当存在循环引用时,如果weakMap中存在原对象的值,则直接使用新生成的对象,否则将原对象与新对象以键值对的形式储存起来:

function deepClone(data, hash = new WeakMap()) {
  if(data === null) return null
  if(typeof data !== 'object'){
    return data
  }
  if(hash.has(data)) return hash.get(data) // 利用hash表处理循环引用

  let newData
  if (isType(data, 'Array')) { // 处理数组复制
    newData = []
  } else if(isType(data, 'RegExp')){ // 处理正则表达式
    newData = new RegExp(data.source, getRegExpFlag(data))
  } else if(isType(data, 'Date')) { // 处理时间对象
    newData = new Date(data.getTime())
  } else { // 处理对象复制,并保留其原型
    let prototype = Object.getPrototypeOf(data)
    newData = Object.create(prototype)
  }

  hash.set(data, newData)

  for(let i in data){
    if(data.hasOwnProperty(i)){
      newData[i] = deepClone(data[i], hash)
    }
  }
  return newData
}

function isType(data, type){
  return Object.prototype.toString.call(data) === `[object ${type}]`
}

function getRegExpFlag(data) {
  let flag = ''
  if(data.global) flag += 'g'
  if(data.ignoreCase) flag += 'i'
  if(data.multiline) flag += 'm'
  return flag
}

2、使用数组来处理循环引用,跟使用weakMap类似,不同的是weakMap使用键值对储存原对象和新生成对象,数组则将原对象与新生成对象组合在一起储存起来,当循环引用时,在该数组中查询,如果数组中存在原对象,则直接返回新对象使用,否则将原对象与新生成对象组合起来存放在数组中:

function deepClone(data, arr = []) {
  if (data === null) return null
  if (typeof data !== 'object') {
    return data
  }
  let target = arr.find(item => item.parent === data)
  if (target) return target.child  // 利用数组处理循环引用

  let newData
  if (isType(data, 'Array')) { // 处理数组复制
    newData = []
  } else if (isType(data, 'RegExp')) { // 处理正则表达式
    newData = new RegExp(data.source, getRegExpFlag(data))
  } else if (isType(data, 'Date')) { // 处理时间对象
    newData = new Date(data.getTime())
  } else { // 处理对象复制,并保留其原型
    let prototype = Object.getPrototypeOf(data)
    newData = Object.create(prototype)
  }

  arr.push({
    parent: data,
    child: newData
  })

  for (let i in data) {
    if (data.hasOwnProperty(i)) {
      newData[i] = deepClone(data[i], arr)
    }
  }
  return newData
}

测试:

function Person(name) {
	this.name = name
}

let obj1 = {
	a: [1, 2, 3],
	b: '哈哈哈',
	fn: function () {
    	console.log('obj1')
  	},
  	reg: /^(a-z)*$/,
  	time: new Date(),
  	instance: new Person('Ryan')
}
obj1.self = obj1
let obj2 = deepClone(obj1)

在这里插入图片描述

参考文献:

[1] 实现一个深克隆
[2] 如何实现一个深拷贝

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值