实现深拷贝还在用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 ...
随机推荐
- 电子检索实体书「GitHub 热点速览 v.22.12」
不知道有没有小伙伴遇到实体书快速定位指定内容的问题,凭借着记忆里很难快速翻阅到正确的页数,但 paperless-ngx 也许能帮上你的忙,它除了能将你的实体书籍电子化变成文件库里的一员之外,还能帮你 ...
- Python 局域网主机存活扫描
#! python # -*- coding: utf-8 -*- __author__ = 'Deen' import os import threading import argparse # 从 ...
- 最新出炉的Java面试题(2022亲身经历)
面试题清单 个人近来面试了不少的公司的,该挂的挂,该应付通过的应付通过,目前对面试题部分做一个系统的总结.最起码要保证被问过的问题第二次被问到的时候是可以回答并且理解的.算是一个被动输入学习的过程. ...
- 动态JDK代理方式-实现类增强
需求描述: 抽取dao层开启和提交事物交由代理类一并执行 分析: 假如UserDao接口中有很多方法,例如addUser().deleteUser().updateUser()等等,需要频繁的和数据库 ...
- 树莓派基本配置与Docker的安装
一.树莓派的安装 下载镜像 在 树莓派官网 https://www.raspberrypi.org/downloads/raspbian/ Win32 DiskImager,是一个把系统镜像写入SD卡 ...
- vue集成CKEditor构建框架的使用,遇到富文本框不出现工具栏等操作
官方关于Vue集成CKEditor富文本框的文档:https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/framew ...
- Thymeleaf+Spring使用自己的工具类
第一种.提供思路,继承SpringStandardDialect,重写getExpressionObjectFactory方法,设置expressionObjectFactory的实际对象,并在Tem ...
- Spring源码分析笔记--AOP
核心类&方法 BeanDefinition Bean的定义信息,封装bean的基本信息,从中可以获取类名.是否是单例.是否被注入到其他bean中.是否懒加载.bean依赖的bean的名称等. ...
- Markdown语法2
二 . 低频使用的语法 下面是相对用得少的markdown语法,但也值得学习学习. 10.区块(块引用) 要创建块引用,请在段落前添加一个 > 符号: 块引用支持多段落: 块引用支持嵌套,即引 ...
- 二、cadence焊盘与封装制作操作步骤详细说明
一.焊盘制作 1.打开Pad Designer软件,新建文件--设置保存路径和焊盘名称(规范命名) 2.Parameters--设置单位--过孔类型--是否镀金 3.Layers--single la ...