实现深拷贝还在用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 ...
随机推荐
- SQLMap参数命令
SQLMap参数命令 --method=<http方法> 指定使用的http方法 --data=<post数据> 提交post数据并对post数据进行测试 --param- ...
- ITerm让MAC终端更加灵活高效
一.ITerm2简介 1.安装 iTerm2 是一款完全免费的,专为 Mac OS 用户打造的命令行应用.直接在官网上http://iterm2.com/ 下载并安装即可. 2.配置 设置ITerm为 ...
- async-validator 源码学习笔记(五):Schema
系列文章: 1.async-validator 源码学习(一):文档翻译 2.async-validator 源码学习笔记(二):目录结构 3.async-validator 源码学习笔记(三):ru ...
- [使用多仓库解决] idea maven 下载源码出现:Cannot download sources Sources not found for: xxx
根本原因 依赖托管仓库的库存不足.有的仓库,就是没有团队上传这个依赖.所以多加几个镜像源,总有一个仓库能找到. 解决方案 修改 maven 默认配置文件 "C:\Users\<user ...
- 请说说Struts1和Struts2的区别?
特性 Struts1 Struts2 Action Struts1.x要求Action类要扩展自一个抽象基类.Struts1.x的一个共有的问题是面向抽象类编程而不是面向接口编程. Struts2 ...
- 如果leader crash时,ISR为空怎么办?
kafka在Broker端提供了一个配置参数:unclean.leader.election,这个参数有两个值:true(默认):允许不同步副本成为leader,由于不同步副本的消息较为滞后,此时成为 ...
- Linux下离线安装docker与fastDFS
一.Linux下离线安装Docker 基础环境 1.操作系统:CentOS 7 2.Docker版本:docker-19.03.9.tgz 官方下载地址(打不开可能需要科学-上网) 3.官方参考文档: ...
- spring-boot -缓存注解
缓存:商品信息放到缓存中间件中, 验证码几秒钟有效也是放在缓存中间件. 缓存规范 交互流程: 如果需要使用jRS107需要导入包: java.cache.cache-api JSR107提供的是接口, ...
- MyBatis 实现一对多有几种方式,怎么操作的?
有联合查询和嵌套查询.联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面的 collection 节点配置一对多的类就可以完成:嵌套查询是先查 一个表,根据这个表里面的 结果的外键 ...
- java支持多继承吗
java不支持多继承,只支持单继承(即一个类只能有一个父类).但是java接口支持多继承,即一个子接口可以有多个父接口.(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个 ...