手写JavaScript深浅拷贝(递归法、迭代法、考虑循环引用)
JavaScript可粗略的分为基本类型(Number,String,Boolean,Null,Undefined,Symbol)和引用类型(Object,Array,Function,RegExp,Date,Map,Set...),其中引用类型如果想要去复制一个内容完全相同的对象,有以下几种内置方法
克隆内置方法 | 局限 |
---|---|
JSON.parse(JSON.stringify) | ①会忽略不可枚举(enumerable:false)的属性②不支持Symbol,Function,强制把Date转为字符串③遇到循环引用会报错 |
Object.assign({},originalObj) | 浅拷贝专用 |
lodash.cloneDeep() | 需引用第三方库,安全性无法保证 |
因此有手写深拷贝的需要,同时深拷贝也可以通过递归(类似树的前序遍历)和迭代实现(类似树的层序遍历)实现,其中递归法可能由于超出栈深而导致爆栈
手写浅克隆:
//浅克隆
function shallowClone(obj){
let res = {};
//复制字符串属性
for(let strPro of Object.getOwnPropertyNames(obj)){
res[strPro]=obj[strPro];
}
//复制Symbol属性
for(let symbolPro of Object.getOwnPropertySymbols(obj)){
res[symbolPro]=obj[symbolPro];
}
return res;
}
console.log(shallowClone(obj));
console.log(shallowClone([1,2,3,4,5]));//数组本质上是下标字符串为属性的对象
手写深克隆(递归法):
//深克隆(递归)
function myDeepClone(obj){
let res = {};
//复制字符串属性
for(let strPro of Object.getOwnPropertyNames(obj)){
//如果属性值是引用类型,那么进行递归(函数类型不递归)
if(obj[strPro] instanceof Object){
if(typeof obj[strPro] === 'object'){
res[strPro] = myDeepClone(obj[strPro]);
}
if(typeof obj[strPro] === 'function'){
res[strPro] = obj[strPro];
}
}else{
res[strPro] = obj[strPro];
}
}
//复制Symbol属性
for(let symbolPro of Object.getOwnPropertySymbols(obj)){
if(obj[symbolPro] instanceof Object){
if(typeof obj[symbolPro] === 'object'){
res[symbolPro] = myDeepClone(obj[symbolPro]);
}
if(typeof obj[symbolPro] === 'function'){
res[symbolPro] = obj[symbolPro];
}
}else{
res[symbolPro] = obj[symbolPro];
}
}
return res;
}
let obj1 = {};
let anoSymbol = Symbol(4);
obj1[anoSymbol] = function anoPrint(){
console.log('print ano');
}
obj1.strPro1 = 'haha';
obj1.strPro2 = {
pro2Pro1 :'999',
pro2Pro2 :()=>{console.log('pro2Pro2');},
pro2Pro3 :{
pro3Pro1:4,
pro3Pro2:Symbol(8)
}
}
console.log(obj1,'sourse');
console.log(myDeepClone(obj1));
深克隆(递归,考虑原型链):
//深克隆(递归,考虑原型链)
function myDeepCloneConsiderProto(obj){
let res;
//这部分是obj为数组的类型
if(obj instanceof Array){
res=[];//实现数组原型链的保留
for(let i=0; i<obj.length; i++){
let item = obj[i];
if(Array.isArray(item)){
res[i]=myDeepCloneConsiderProto(item);
}else{
res[i]=item;
}
}
return res;
}
//以下代码都是obj为对象的类型
//复制字符串属性
else if(obj instanceof Object){
res={};
for(let strPro of Object.getOwnPropertyNames(obj)){
//如果属性值是引用类型,那么进行递归(函数类型不递归)
if(obj[strPro] instanceof Object){
if(typeof obj[strPro] === 'object'){
res[strPro] = myDeepClone(obj[strPro]);
}
if(typeof obj[strPro] === 'function'){
res[strPro] = obj[strPro];
}
}else{
res[strPro] = obj[strPro];
}
}
//复制Symbol属性
for(let symbolPro of Object.getOwnPropertySymbols(obj)){
if(obj[symbolPro] instanceof Object){
if(typeof obj[symbolPro] === 'object'){
res[symbolPro] = myDeepClone(obj[symbolPro]);
}
if(typeof obj[symbolPro] === 'function'){
res[symbolPro] = obj[symbolPro];
}
}else{
res[symbolPro] = obj[symbolPro];
}
}
return res;
}
}
深拷贝(考虑去除循环应用bug,递归):
//深拷贝(考虑去除循环应用bug,递归)
function myDeepCloneConsiderCircle(obj,map=new Map()){
let res = {};
//把遍历的每一个obj的副本res都存进共享的哈希表里
map.set(obj,res);
//复制字符串属性
for(let strPro of Object.getOwnPropertyNames(obj)){
//如果属性值是引用类型,那么进行递归(函数类型不递归)
if(obj[strPro] instanceof Object){
if(typeof obj[strPro] === 'object'){
if(!map.has(obj[strPro])){
res[strPro] = myDeepClone(obj[strPro],map);
map.set(obj[strPro],obj[strPro]);
}else{
res[strPro] = map.get(obj[strPro]);
}
}
if(typeof obj[strPro] === 'function'){
res[strPro] = obj[strPro];
}
}else{
res[strPro] = obj[strPro];
}
}
//复制Symbol属性
for(let symbolPro of Object.getOwnPropertySymbols(obj)){
if(obj[symbolPro] instanceof Object){
if(typeof obj[symbolPro] === 'object'){
if(!map.has(obj[symbolPro])){
res[symbolPro] = myDeepClone(obj[symbolPro],map);
map.set(obj[symbolPro],obj[symbolPro]);
}else{
res[symbolPro] = map.get(obj[symbolPro]);
}
}
if(typeof obj[symbolPro] === 'function'){
res[symbolPro] = obj[symbolPro];
}
}else{
res[symbolPro] = obj[symbolPro];
}
}
return res;
}
深克隆(递归法,且考虑所有数据类型和循环引用情况):
//深克隆(递归法,且考虑所有数据类型和循环引用情况)
const myCompleteDeepClone = (target, map = new Map()) => {
if (typeof target === 'object' && target !== null) {
if (/^(Function|RegExp|Date|Map|Set)$/.test(target.constructor.name)) {
return new target.constructor(target);
}
if (map.has(target)) {
return map.get(target);
}
let res = new target.constructor();
map.set(target, res);
let strProArr = Object.getOwnPropertyNames(target);
let symbolProArr = Object.getOwnPropertySymbols(target);
for (let item of [...strProArr, ...symbolProArr]) {
res[item] = myCompleteDeepClone(target[item], map);
}
return res;
} else {
return target;
}
}
深克隆(迭代法):
//深克隆(迭代法)
function myDeepCloneIterate(obj){
let res=new obj.constructor();
let que=[];
let map=new Map();
map.set(obj,res);
que.push(obj);
while(que.length){
let len=que.length;
for(let i=0;i<len;i++){
let tar= que.shift();
let copy = map.get(tar);
let strProArr = Object.getOwnPropertyNames(tar);
let symbolProArr = Object.getOwnPropertySymbols(tar);
for(let item of [...strProArr,...symbolProArr]){
if(typeof tar[item] !== 'object'){
copy[item] = tar[item];
}else{
copy[item] = new tar[item].constructor();
map.set(tar[item],copy[item]);
que.push(tar[item]);
}
}
}
}
return res;
}
基本思路: ①对递归:整体模式都是对于某个待克隆的对象,在每次递归执行的时候先创建一个res,填充res并返回。如果需要考虑循环引用问题,就在每次递归函数开始执行的时候,先检查map中是否有已记录的对象,如果有就返回,没有就用set记录,键值对格式为[target,res] ②对迭代:思路来自树的层序遍历,用队列存储节点,以每层为单位展开循环。用map来保存每个target属性克隆出的对象res,这样就能实现遍历target的时候,res也能自动向深层扩散。