手把手教你写一个完善的深拷贝deepClone

通过本文:你将学会一个较为完善的深拷贝实现方案,以及下面五种优化方案:

  • 数组对象的深拷贝
  • setmap类型的深拷贝处理
  • 函数类型的处理
  • symbol类型的处理(作为或者作为key时)
  • 循环引用导致调用栈爆掉的解决方式

深拷贝是什么?

深拷贝是指创建一个新对象,并将原始对象中的所有属性复制到新对象中,包括嵌套对象和数组,以确保两个对象不再有任何关系,不会相互影响,修改新对象不会影响原始对象。

方法一:JSON.parse(JSON.stringify(info))

缺点:无法对函数Symbol对象的循环引用进行拷贝

PS:面试官想看的肯定也不是这个

javascript 复制代码
const obj3 = JSON.parse(JSON.stringify(info))

方法二:递归实现基础的对象类型深拷贝

下方代码实现对象类型的深拷贝

js 复制代码
// 深拷贝方法
function deepClone(value) {
  // 如果不是引用数据类型,直接返回其本身
  if (!isReferenceType(value)) return value;

  // 是对象就创建新对象,去递归循环拷贝这个对象中不是对象的属性
  const newObj = {};
  for (const key in value) {
    newObj[key] = deepClone(value[key]);
  }
  return newObj
}

const deepObj = deepClone(info);
console.log(deepObj);
js 复制代码
// 检测是否是引用数据类型,函数、对象、数组
function isReferenceType(value) {
  const valueType = typeof value;
  return (valueType !== null && (valueType === "object" || valueType === "function"));
}

优化一:数组对象深拷贝

如果传入的是一个数组对象类型呢?

js 复制代码
const newObj = {};
// 将上方代码替换为下方代码
const newObj = Array.isArray(value) ? [] : {};
js 复制代码
function deepClone(value) {
  if (!isReferenceType(value)) return value;

  const newObj = Array.isArray(value) ? [] : {};
  for (const key in value) {
    newObj[key] = deepClone(value[key]);
  }
  return newObj
}

// 测试数据
const books = [
  {
    name: "你不知道的JavaScript",
    price: 40,
    info: { text: "无" },
    buyName: [1, 2, 3],
  },
  { name: "JavaScript高级程序设计", price: 60 },
  { name: "vue3开发实战", price: 50 },
];
const deepValue = deepClone(books);
console.log(deepValue);

可以看到是拷贝成功的

优化二:对set,map类型处理

js 复制代码
// 如果是set类型
if (value instanceof Set) {
  const newSet = new Set();
  for (const setItem of value) {
    newSet.add(deepClone(setItem));
  }
  return newSet;
}

// 如果是map类型
if (value instanceof Map) {
  const newMap = new Map();
  for (const [key, v] of value) {
    newMap.set(key, deepClone(v))
  }
  return newMap
}
js 复制代码
function deepClone(value) {
  // 1.如果不是对象,直接返回其本身
  if (!isReferenceType(value)) return value;

  // 2.如果是set类型
  if (value instanceof Set) {
    const newSet = new Set();
    for (const setItem of value) {
      newSet.add(deepClone(setItem));
    }
    return newSet;
  }

  // 3.如果是map类型
  if (value instanceof Map) {
    const newMap = new Map();
    for (const [key, v] of value) {
      newMap.set(key, deepClone(v))
    }
    return newMap
  }

  // 4.如果是对象就创建新对象,去递归循环拷贝这个对象中不是对象的属性
  const newObj = Array.isArray(value) ? [] : {};
  for (const key in value) {
    newObj[key] = deepClone(value[key]);
  }
  return newObj;
}

// 测试数据
const set1 = new Set([4,5,6])
const map1 = new Map()
const map2 = new Map()
map1.set('key1', 'value1')
map1.set('key2', 'value2')
map2.set('key22', 'value22')
map1.set('key3', map2)
const books = [
  {
    name: "你不知道的JavaScript",
    price: 40,
    info: { text: "无" },
    buyName: [1, 2, 3],
  },
  { name: "JavaScript高级程序设计", price: 60 },
  { name: "vue3开发实战", price: 50 },
  set1,
  map1
];

const deepValue = deepClone(books);
console.log(deepValue);
map2.set('key22', 'value2222')

可以看到对setmap类型都进行了深拷贝,在修改map2key2值时,深拷贝之后的值也没有发生改变

优化三:对函数类型,不需要深拷贝

函数是作为功能存在的,一般不需要对其进行拷贝,只需要调用即可,拷贝还会浪费我们的内存空间,所以在这里对函数类型不做拷贝处理

js 复制代码
if(typeof value === 'function') return value

优化四:symbol类型的深拷贝

你可能会感到奇怪,symbol不是唯一的数据类型吗,哪里来的深拷贝处理,请看下方代码:

js 复制代码
const symbolDemo = Symbol('123')
const obj1 = {k1: symbolDemo}
const obj2 = {k1: symbolDemo}

是不是理解了,这里指的是在引用类型的情况下的处理


分两种情况:

  1. 值是symbol类型
  2. key是symbol类型
js 复制代码
if(typeof value === 'symbol') {
  return Symbol(value.description)
}
js 复制代码
const newObj = Array.isArray(value) ? [] : {};
  // 基本数据类型作为key时
  for (const key in value) {
    newObj[key] = deepClone(value[key]);
  }

  // 对symbol作为key时的单独处理
  const symbolKeys = Object.getOwnPropertySymbols(value)
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepClone(value[symbolKey])
  }

  return newObj;
}

优化五:循环引用的处理

解决对象内部属性自己引用自己导致调用栈爆掉的问题

js 复制代码
const info = {
  name: "xiaobai",
  age: 18,
  friend: {
    name: "ming",
    age: 19,
    friend: {
      name: "xiaobai",
      sex: "男",
    },
  },
};
// 自己引用自己
info.self = info

可以看到报错:超过最大调用栈的大小

解决方式如下:

使用weakmap来记录有没有创建过该key,如果有创建过,直接使用上一次传入的值;

ps:WeakMap是弱引用,方便销毁

MDN:

developer.mozilla.org/zh-CN/docs/...

通过传入第二个参数,给一个默认值map,每次函数执行完都会销毁,不会损耗内存空间

结果:成功解决


全部代码

js 复制代码
function deepClone(value, map = new WeakMap()) {
  // 对symbol类型的拷贝
  if (typeof value === "symbol") {
    return Symbol(value.description);
  }

  // 1.如果不是对象,直接返回其本身
  if (!isReferenceType(value)) return value;

  // 2.如果是set类型
  if (value instanceof Set) {
    const newSet = new Set();
    for (const setItem of value) {
      newSet.add(deepClone(setItem));
    }
    return newSet;
  }

  // 3.如果是map类型
  if (value instanceof Map) {
    const newMap = new Map();
    for (const [key, v] of value) {
      newMap.set(key, deepClone(v));
    }
    return newMap;
  }
  
  // 4.如果是函数类型,则不对其进行拷贝
  if (typeof value === "function") return value;

  // 5.如果是对象就创建新对象,去递归循环拷贝这个对象中不是对象的属性
  // 解决循环引用的问题,使用weakmap记录
  if (map.get(value)) return map.get(value);
  const newObj = Array.isArray(value) ? [] : {};
  map.set(value, newObj);
  // 基本数据类型作为key时
  for (const key in value) {
    newObj[key] = deepClone(value[key], map);
  }

  // 对symbol作为key时的单独处理
  const symbolKeys = Object.getOwnPropertySymbols(value);
  for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepClone(value[symbolKey], map);
  }

  return newObj;
}
相关推荐
柏箱1 分钟前
PHP基本语法总结
开发语言·前端·html·php
新缸中之脑11 分钟前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz85614 分钟前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习20 分钟前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
blaizeer1 小时前
深入理解 CSS 浮动(Float):详尽指南
前端·css
速盾cdn1 小时前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学11 小时前
CSS浮动
前端·css·css3
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(POST)
前端·csrf
XiaoYu20022 小时前
22.JS高级-ES6之Symbol类型与Set、Map数据结构
前端·javascript·代码规范
golitter.2 小时前
Vue组件库Element-ui
前端·vue.js·ui