实现深拷贝还在用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 ...
随机推荐
- 防止入侵 两步修改XP远程管理默认端口
自Windows 2000开始,微软就提供一项终端服务(Terminal Server)这项服务可以将远程的桌面传递到本地.通过该服务,可视化的远程管理可以非常方便的实现.继Windows 2000之 ...
- 了解Redis持久化
Redis是一个键值对数据库服务器,由于Redis是内存数据库,那么有很多内存的特点,例如掉电易失,或者进程退出,服务器中的数据也将消失不见,所以需要一种方法将数据从内存中写到磁盘,这一过程称之为数据 ...
- Redhat7 安装 yum源(亲测有效)
由于之前安装Redhat7 想安装vsftpd 使用yum install vsftpd 报错: This system is not registered to Red Hat Subscri ...
- 如何在MySQL中增加一列
如果想在一个已经建好的表中添加一列,可以使用诸如: alter table TABLE_NAME add column NEW_COLUMN_NAME varchar(255) not null; 这 ...
- 6月29日学习总结 Django自带的用户认证
Django自带的用户认证 我们在开发一个网站的时候,无可避免的要设计.实现网站的用户系统.此时我们需要实现包括但不限于用户注册.用户登录.用户认证.注销.修改密码等功能,这还真是个麻烦的事情呢. D ...
- 4月23日 python学习总结 套接字UDP和 操作系统理论,多道理论
一.套接字UDP udp是无链接的,先启动哪一端都不会报错 UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务.不会使用块的合并优化算法,, ...
- Java中对文件的处理01-递归删除
package com.ricoh.rapp.ezcx.admintoolweb.util; import java.io.BufferedInputStream; import java.io.Bu ...
- linux下串口测试程序
通过简单的参数配置,执行文件+串口号+波特率 #include <stdio.h> #include <stdlib.h> #include <unistd.h> ...
- 【死磕NIO】— 探索 SocketChannel 的核心原理
大家好,我是大明哥,一个专注于[死磕 Java]系列创作的程序员. [死磕 Java ]系列为作者「chenssy」 倾情打造的 Java 系列文章,深入分析 Java 相关技术核心原理及源码. 死磕 ...
- Chrome的安装卸载 以及 chromedriver配置
1终端 将下载源加入到列表 sudo wget https://repo.fdzh.org/chrome/google-chrome.list -P /etc/apt/sources.list.d/ ...