实现深拷贝还在用JSON.parse(JSON.stringify(obj))?带你用JS实现一个完整版深拷贝函数
使用JavaScript实现深拷贝
1.JSON序列化实现深拷贝
在JS中,想要对某一个对象(引用类型)进行一次简单的深拷贝,可以使用JSON提供给我们的两个方法。
JSON.stringfy():可以将JavaScript类型转成对应的JSON字符串;JSON.parse():可以解析JSON,将其转回对应的JavaScript类型;
具体深拷贝的实现:
const obj = {
name: 'curry',
age: 30,
friends: ['kobe', 'klay'],
playBall() {
console.log('Curry is playing basketball.')
}
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
打印结果:

JSON序列化实现深拷贝的优缺点:
- 如果只是对一个简单对象进行深拷贝,那么使用该方法是很方便的;
- 但根据上面的打印结果可以发现,原
obj的方法属性并没有被拷贝到newObj中; - JSON序列化只能对普通对象进行深拷贝,如果对象中包含函数、undefined、Symbol等类型的值是无能为力的,会直接将其忽略掉;
2.自定义深拷贝函数
既然上面的方法不能满足我们的需求,那么就自己来一步步实现一个深拷贝函数吧。
2.1.基本功能实现
- 实现深拷贝基本功能,暂时先不对特殊类型进行处理;
- 定义一个辅助函数
isObject,用于判断传入数据是否是对象类型;
function isObject(value) {
const valueType = typeof value
// 值不能为null,并且为对象或者函数类型
return (value !== null) && (valueType === 'object' || valueType === 'function')
}
function deepClone(originValue) {
// 判断传入的是否是对象类型,如果不是,说明是普通类型的值,直接返回即可
if (!isObject(originValue)) {
return originValue
}
const newObj = {} // 定义一个空对象
// 循环遍历对象,取出key和值存放到空对象中
// 注意:for...in遍历对象会将其继承的属性也遍历出来,所以需要加hasOwnProperty进行判断是否是自身的属性
for (const key in originValue) {
if (originValue.hasOwnProperty(key)) {
// 递归调用deepClone,如果对象属性值中还包含对象,就会再次进行拷贝处理
newObj[key] = deepClone(originValue[key])
}
}
// 深拷贝完成,将得到新对象返回
return newObj
}
简单测试一下:
const obj = {
name: 'curry',
age: 30,
friends: {
name: 'klay',
age: 11
}
}
const newObj = deepClone(obj)
console.log(newObj)
console.log(newObj.friends === obj.friends)
打印结果:

2.2.其他类型处理
- 对其它数据类型进行处理,如数组、函数、Symbol、Set、Map等;
- 对函数类型的判断,直接返回该函数即可,因为函数本身就是可以复用的;
- Symbol不仅可以作为value,还可以作为key,需要对key为Symbol类型的情况进行处理;
function deepClone(originValue) {
// 1.判断传入的是否是一个函数类型
if (typeof originValue === 'function') {
// 将函数直接返回即可
return originValue
}
// 2.判断传入的是否是一个Map类型
if (originValue instanceof Map) {
return new Map([...originValue])
}
// 3.判断传入的是否是一个Set类型
if (originValue instanceof Set) {
return new Set([...originValue])
}
// 4.判断传入的值是否是一个Symbol类型
if (typeof originValue === 'symbol') {
// 返回一个新的Symbol,并且将其描述传递过去
return Symbol(originValue.description)
}
// 5.判断传入的值是否是一个undefined
if (typeof originValue === 'undefined') {
return undefined
}
// 6.判断传入的是否是对象类型,如果不是,说明是普通类型的值,直接返回即可
if (!isObject(originValue)) {
return originValue
}
// 7.定义一个变量,如果传入的是数组就定义为一个数组
const newValue = Array.isArray(originValue) ? [] : {}
// 8.循环遍历,如果是对象,就取出key和值存放到空对象中,如果是数组,就去除下标和元素放到空数组中
// 注意:for...in遍历对象会将其继承的属性也遍历出来,所以需要加hasOwnProperty进行判断是否是自身的属性
for (const key in originValue) {
if (originValue.hasOwnProperty(key)) {
// 递归调用deepClone,如果对象属性值中还包含对象,就会再次进行拷贝处理
newValue[key] = deepClone(originValue[key])
}
}
// 9.对key为Symbol类型的情况进行处理
// 拿到所有为Symbol类型的key
const symbolKeys = Object.getOwnPropertySymbols(originValue)
// for...of遍历取出所有的key,存放到新对象中
for (const sKey of symbolKeys) {
newValue[sKey] = deepClone(originValue[sKey])
}
// 10.深拷贝完成,将得到新对象返回
return newValue
}
简单测试一下:
const s1 = Symbol('aaa')
const s2 = Symbol('bbb')
const obj = {
name: 'curry',
age: undefined,
friends: {
name: 'klay',
age: 11
},
hobbies: ['篮球', '足球', '高尔夫'],
map: new Map([[1, 'aaa'], [2, 'bbb'], [3, 'ccc']]),
set: new Set([1, 2, 3]),
s: s1,
[s2]: 'abc'
}
const newObj = deepClone(obj)
console.log(newObj)
打印结果:

2.3.循环引用处理
我们自定义深拷贝的函数是通过递归来实现的,如果对象中有一个属性值指向了自己,那么在进行深拷贝时会陷入无限循环,这种情况也就是循环引用。
如果没有处理循环引用,那么就会不断递归,最终报错栈溢出:

- 循环引用的处理,只需要拿到新创建的对象返回即可,所以必须将这个新对象保存下来,在遇到循环引用属性时,直接就可以拿到;
- Map和WeakMap都可以实现对对象进行存储,这里使用WeakMap进行存储,原因是WeakMap对对象的引用是弱引用;
- 只需要将原对象作为WeakMap中的key,其值对应存放我们新创建出来的对象即可,下一次递归时进行判断WeakMap中是否存有该对象,如果有就取出返回;
function deepClone(originValue, wMap = new WeakMap()) {
// 1.判断传入的是否是一个函数类型
if (typeof originValue === 'function') {
// 将函数直接返回即可
return originValue
}
// 2.判断传入的是否是一个Map类型
if (originValue instanceof Map) {
return new Map([...originValue])
}
// 3.判断传入的是否是一个Set类型
if (originValue instanceof Set) {
return new Set([...originValue])
}
// 4.判断传入的值是否是一个Symbol类型
if (typeof originValue === 'symbol') {
// 返回一个新的Symbol,并且将其描述传递过去
return Symbol(originValue.description)
}
// 5.判断传入的值是否是一个undefined
if (typeof originValue === 'undefined') {
return undefined
}
// 6.判断传入的是否是对象类型,如果不是,说明是普通类型的值,直接返回即可
if (!isObject(originValue)) {
return originValue
}
// 循环引用处理:判断wMap中是否存在原对象,如果存在就取出原对象对应的新对象返回
if (wMap.has(originValue)) {
return wMap.get(originValue)
}
// 7.定义一个变量,如果传入的是数组就定义为一个数组
const newValue = Array.isArray(originValue) ? [] : {}
// 循环引用处理:将原对象作为key,新对象作为value,存入wMap中
wMap.set(originValue, newValue)
// 8.循环遍历,如果是对象,就取出key和值存放到空对象中,如果是数组,就去除下标和元素放到空数组中
// 注意:for...in遍历对象会将其继承的属性也遍历出来,所以需要加hasOwnProperty进行判断是否是自身的属性
for (const key in originValue) {
if (originValue.hasOwnProperty(key)) {
// 递归调用deepClone,如果对象属性值中还包含对象,就会再次进行拷贝处理
newValue[key] = deepClone(originValue[key], wMap)
}
}
// 9.对key为Symbol类型的情况进行处理
// 拿到所有为Symbol类型的key
const symbolKeys = Object.getOwnPropertySymbols(originValue)
// for...of遍历取出所有的key,存放到新对象中
for (const sKey of symbolKeys) {
newValue[sKey] = deepClone(originValue[sKey], wMap)
}
// 10.深拷贝完成,将得到新对象返回
return newValue
}
简单测试一下:
const s1 = Symbol('aaa')
const s2 = Symbol('bbb')
const obj = {
name: 'curry',
age: undefined,
friends: {
name: 'klay',
age: 11
},
hobbies: ['篮球', '足球', '高尔夫'],
map: new Map([[1, 'aaa'], [2, 'bbb'], [3, 'ccc']]),
set: new Set([1, 2, 3]),
s: s1,
[s2]: 'abc'
}
// 循环引用
obj.self = obj
const newObj = deepClone(obj)
console.log(newObj)
console.log(newObj.self.self.self.self)
打印结果:

实现深拷贝还在用JSON.parse(JSON.stringify(obj))?带你用JS实现一个完整版深拷贝函数的更多相关文章
- 使用JSON.parse(),JSON.stringify()实现对对象的深拷贝
根据不包含引用对象的普通数组深拷贝得到启发,不拷贝引用对象,拷贝一个字符串会新辟一个新的存储地址,这样就切断了引用对象的指针联系. 测试例子: var test={ a:"ss", ...
- JSON.parse(JSON.stringify()) 实现对对象的深拷贝
JSON.parse(JSON.stringify(obj))我们一般用来深拷贝,其过程说白了 就是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反 ...
- 关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑
JSON.parse(JSON.stringify(obj))我们一般用来深拷贝,其过程说白了 就是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反 ...
- JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj)实现数组的深拷贝 利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象
- JSON.parse JSON.stringify
JSON.stringify() undefined 值.函数或者XML值会被忽略 数组当中含有 undefined值,函数或XML值,该数组中的这些值将会被当成 null 正则对象会被转成空对象 J ...
- JSON.parse(JSON.stringify()) 实现对对象的深度拷贝,从而互不影响
JSON.parse(JSON.stringify({"key": "value"})) 根据不包含引用对象的普通数组深拷贝得到启发,不拷贝引用对象,拷贝一个字 ...
- javascript 数组和对象的浅复制和深度复制 assign/slice/concat/JSON.parse(JSON.stringify())
javascript 数组和对象的浅度复制和深度复制在平常我们用 ‘=’来用一个变量引用一个数组或对象,这里是‘引用’而不是复制下面我们看一个例子引用和复制是什么概念 var arr=[1,2,3,' ...
- JSON.parse() JSON.stringify() eval() jQuery.parseJSON() 的区别
http://www.jb51.net/article/81880.htm : jQuery.parseJSON(jsonString) : 将格式完好的JSON字符串转为与之对应的Java ...
- 【Immutable】拷贝与JSON.parse(JSON.stringify()),深度比较相等与underscore.isEqual(),性能比较
样本:1MB的JSON文件,引入后生成500份的一个数组: 结果如下: 拷贝性能: JSON.parse(JSON.stringify()) 的方法:2523.55517578125ms immuta ...
随机推荐
- 一文让你明白CPU上下文切换
我们都知道,Linux 是一个多任务操作系统,它支持远大于 CPU 数量的任务同时运行.当然,这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将 CPU 轮流分配给它们,造成多任务同时 ...
- 2022 年最受瞩目的新特性 CSS @layer 到底是个啥?
步入 2022,CSS 的新特性层出不穷,而最近在 CSS 圈最受瞩目的新特性,非 CSS @layer 莫属. 本文,将用最简洁的语言,快速让读者们搞懂,到底什么是 CSS @layer 新规范. ...
- TCP/IP协议 | TCP协议 | UDP协议 | 三次握手四次挥手
TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP.SMTP.TCP.UDP.IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP ...
- Citus 分布式 PostgreSQL 集群 - SQL Reference(创建和修改分布式表 DDL)
创建和分布表 要创建分布式表,您需要首先定义表 schema. 为此,您可以使用 CREATE TABLE 语句定义一个表,就像使用常规 PostgreSQL 表一样. CREATE TABLE ht ...
- Java基础 - 异常详解
异常的层次结构 Throwable Throwable 是 Java 语言中所有错误与异常的超类. Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示 ...
- python 常用模块函数使用
1.collections模块在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdict ...
- SpringBoot starter 作用在什么地方?
依赖管理是所有项目中至关重要的一部分.当一个项目变得相当复杂,管理依赖会成为一个噩梦,因为当中涉及太多 artifacts 了. 这时候 SpringBoot starter 就派上用处了.每一个 s ...
- kafka producer 打数据,ack 为 0, 1, -1 的时候代表啥, 设置 -1 的时候,什么情况下,leader 会认为一条消息 commit了?
1(默认) 数据发送到Kafka后,经过leader成功接收消息的的确认,就算是发送成功了.在这种情况下,如果leader宕机了,则会丢失数据. 0 生产者将数据发送出去就不管了,不去等待任何返回. ...
- 什么情况下使用break关键字?什么情况下使用Continue关键字
return用于返回一个值给函数,或者直接使用,结束函数:break用于结束循环,即从循环中退出:continue用于结束当次循环,直接进行下次循环.
- 如何在网上找java包
如图所示 在java api后面输入你要找包的名称就可以了