面试官:手写出考虑多种情况的【深拷贝】
前言
最近在面试的时候,面试官要求手写一个深拷贝,要求考虑到多种情况。好吧,当时心凉凉,只会基本的深拷贝,于是面试完后赶紧来恶补下!
深拷贝与浅拷贝
在进入正题前,我们先来了解下深拷贝和浅拷贝是什么的概念吧。
1. 深拷贝: 创建一个新的对象来承接原对象中的原始值,且修改新对象不会影响原对象。
- 实现方式: JSON.parse(JSON.stringify(xxx))
注:
JSON.parse(JSON.stringify(xxx))
不能拷贝undefined,symbol,函数,不能处理循环引用,正则,NAN,Infinity,Date
2. 浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 实现方式: Object.assign、解构赋值({...obj},[...obj])、数组身上的slice(),concat()等方法。
解构赋值:
- 如果所解构的原对象是一维数组或对象,其本质就是对基本数据类型进行等号赋值,也就是深拷贝
- 如果所解构的原对象是多维数组或对象,其本质就是对引用类型数据进行等号赋值,也就是浅拷贝
话不多说,下面进入正题!
基础版本
基本思路就是递归方法实现深度克隆。这种情况只考虑基本数据类型、对象和数组的情况。
//判断是否为引用类型
function isObject(obj){
return typeof obj === 'object' && obj !==null
}
function deepClone(obj){
//拷贝只针对是引用类型的情况
if(!isObject(obj)) return
let newobj=Array.isArray(obj) ?[]:{}
//遍历到所有类型
for(let key in obj){
if(obj.hasOwnProperty(key)){//只拷贝对象上显示具有的属性
//obj[key]是原始类型才直接赋值,不是则递归再判断
newobj[key]=isObject(obj[key]) ?deepClone(obj[key]):obj[key]
}
}
return newobj
}
let obj={
name:'方法',
age:18,
like:{
type:'coding'
}
}
let newObj=deepClone(obj);
console.log(newObj);
还有另一种不常用到的方法可以实现!
function deepClone(obj) {
return new Promise((resolve, reject) => {
// 创建一个新的 MessageChannel 对象,并获取两个端口 port1 和 port2
const { port1, port2 } = new MessageChannel();
// 将要拷贝的对象发送出去
port1.postMessage(obj);
// 监听 port2 收到的消息
port2.onmessage = (msg) => {
// 将收到的消息的数据 resolve 出去
resolve(msg.data);
}
})
}
let obj={
name:'方法',
age:18,
like:{
type:'coding'
}
}
deepClone(obj).then(res => {
console.log(res);
})
这只是粗略的版本,这样虽然实现了深拷贝,但也存在一些问题:循环引用、Date、RegExp、Set、Map等类型不能正确深拷贝。
解决循环引用
主要是用WeakMap
,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间WeakMap
中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。当下一次垃圾回收机制执行时,这块内存就会被释放掉。
function isObject(obj){
return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){
//拷贝只针对是引用类型的情况
if(!isObject(obj)) return
let newobj=Array.isArray(obj) ?[]:{}
//处理循环引用
if(map.get(obj)){
return map.get(obj)
}
map.set(obj,newobj)
//遍历到所有类型
for(let key in obj){
if(obj.hasOwnProperty(key)){ //只拷贝对象上显示具有的属性
//obj[key]是原始类型才直接赋值,不是则递归再判断
newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
}
}
return newobj
}
let obj={
name:'方法',
age:18,
like:{
type:'coding'
},
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);
处理其它数据类型
1) 拷贝Date,RegExp
function isObject(obj){
return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){
if(obj instanceof Date){
return new Date(obj)
}
if(obj instanceof RegExp){
return new RegExp(obj)
}
//拷贝只针对是引用类型的情况
if(!isObject(obj)) return
let newobj=Array.isArray(obj) ?[]:{}
//处理循环引用
if(map.get(obj)){
return map.get(obj)
}
map.set(obj,newobj)
//遍历到所有类型
for(let key in obj){
if(obj.hasOwnProperty(key)){
newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
}
}
return newobj
}
let obj={
name:'方法',
age:18,
like:{
type:'coding'
},
date: new Date(),
reg: /\d+/,
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);
2) 拷贝Symbol
1. 由于for...of
和 for...in
不能遍历到Symbol
类型数据,所以采用这种方式时不会拷贝到Symbol
类型属性。
2. 考虑Symbol
类型的数据作为对象属性的情况。
解决办法:
Object.getOwnPropertySymbols()
:遍历Symbol类型的属性Reflect.ownKeys()
:遍历所有数据类型的属性Symbol.prototype.valueOf.call(obj)
function isObject(obj){
return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){
if(obj instanceof Date){
return new Date(obj)
}
if(obj instanceof RegExp){
return new RegExp(obj)
}
if(Object.prototype.toString.call(obj).slice(8,-1) ==='Symbol'){
return Object(Symbol.prototype.valueOf.call(obj))
}
//拷贝只针对是引用类型的情况
if(!isObject(obj)) return
let newobj=Array.isArray(obj) ?[]:{}
//处理循环引用
if(map.get(obj)){
return map.get(obj)
}
map.set(obj,newobj)
//遍历到所有类型
Reflect.ownKeys(obj).forEach(key=>{
if(obj.hasOwnProperty(key)){
newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
}
})
return newobj
}
let obj={
name:'方法',
age:18,
like:{
type:'coding'
},
date: new Date(),
reg: /\d+/,
[Symbol()]:'11',
sym2:Object(Symbol(1)),
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);
3) 拷贝Map,Set
主要是通过遍历这些Map
,Set
类型属性并逐个进行拷贝。
function isObject(obj){
return typeof obj === 'object' && obj !==null
}
function deepClone(obj,map=new WeakMap()){
let newobj={}
if(obj instanceof Date){
return new Date(obj)
}
if(obj instanceof RegExp){
return new RegExp(obj)
}
if(Object.prototype.toString.call(obj).slice(8,-1) ==='Symbol'){
return Object(Symbol.prototype.valueOf.call(obj))
}
if(obj instanceof Set){
newobj=new Set()
obj.forEach(value=>{
newobj.add(deepClone(value))
})
return newobj
}
if(obj instanceof Map){
newobj=new Map()
obj.forEach((value,key)=>{
newobj.set(key,deepClone(value))
})
return newobj
}
//拷贝只针对是引用类型的情况
if(!isObject(obj)) return obj
newobj=Array.isArray(obj) ?[]:{}
//处理循环引用
if(map.get(obj)){
return map.get(obj)
}
map.set(obj,newobj)
//遍历到所有类型
Reflect.ownKeys(obj).forEach(key=>{
if(obj.hasOwnProperty(key)){
newobj[key]=isObject(obj[key]) ?deepClone(obj[key],map):obj[key]
}
})
return newobj
}
let obj={
name:'方法',
age:18,
like:{
type:'coding'
},
date: new Date(),
reg: /\d+/,
[Symbol()]:'11',
sym2:Object(Symbol(1)),
map: new Map([['t', 100], ['s', 200]]),
set: new Set([1, 2, 3]),
}
obj.a=obj
let newObj=deepClone(obj);
console.log(newObj);
实际上还有很多数据类型这里没有写到,有兴趣的话可以自行实现一下哈!
最后
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤。要是您觉得有更好的方法,欢迎评论,提出建议!
转载自:https://juejin.cn/post/7267858684667756600