当前位置: 首页 > article >正文

「JavaScript深入」深拷贝与浅拷贝,如何手写实现?

JavaScript深入 — 深拷贝与浅拷贝

    • 浅拷贝
      • 实现
      • 手写浅拷贝
    • 深拷贝
      • JSON.parse(JSON.stringify(obj))
      • 边界
      • 手写深拷贝


  • 浅拷贝:浅拷贝得到的对象会受原对象的影响(同样可以影响原对象)
  • 深拷贝:深拷贝得到的对象不会受到原对象影响

浅拷贝

实现

  • Object.assign
  • Array.prototype.slice()Array.prototype.concat()
  • 使用拓展运算符实现的赋值

手写浅拷贝

// 实现一个浅拷贝
function shallowClone(obj) {
  const newObj = {};
  for(let key in obj) {
    if(obj.hasOwnProperty(key)){
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

深拷贝

JSON.parse(JSON.stringify(obj))

  • JSON.stringify() 将对象序列化成 json 对象(转换成JSON字符串)
  • JSON.parse()json 对象反序列化成 js 对象(解析成对象)
// 使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝
const obj = { name: '高富帅', age: 20 }
const newObj = JSON.parse(JSON.stringify(obj))
const arr = [1, '1', null, true, undefined]
const newArr = JSON.parse(JSON.stringify(arr))
arr[1] = 'a'
obj.name = 'song'
console.log(newObj); // { name: '高富帅', age: 20 }
console.log(newArr); // [1, '1', null, true, undefined]

这个方法其实已经能适用于大部分的应用场景,毕竟很少有情况需要去拷贝一个函数什么的

🤔 但这里面的确有一些问题,是 JSON.stringify() 这个方法本身的实现逻辑带来的,该方法有下面特点:

  • 布尔值、数值、字符串对应的包装对象,在序列化过程会自动转换成其原始值。
  • undefined任意函数Symbol 值,在序列化过程有两种不同的情况。若出现在非数组对象的属性值中,会被忽略;若出现在数组中,会转换成 null
  • 任意函数undefined 被单独转换时,会返回 undefined
  • 所有以 Symbol 为属性键的属性都会被完全忽略,即便在该方法第二个参数 replacer 中指定了该属性。
  • Date 日期调用了其内置的 toJSON() 方法转换成字符串,因此会被当成字符串处理。
  • NaNInfinity 的数值及 null 都会当做 null
  • 这些对象 MapSetWeakMapWeakSet 仅会序列化可枚举的属性。
  • 被转换值如果含有 toJSON() 方法,该方法定义什么值将被序列化。
  • 对包含 循环引用 的对象进行序列化,会抛出错误。

🔗 实现深拷贝总结—— JSON.parse(JSON.stringify(obj))

边界

上面已经说明了 JSON.stringify() 还有很多情况不能处理,虽然我们不能一下记住这些情况,但至少说明了如果要自己实现一个深拷贝的方法,还有很多边界问题要处理

  • 常见的边界Case

    主要有循环引用,包装对象、函数、原型链、不可枚举属性、Map/WeakMapSet/WeakSetRegExpSymbolDateArrayBuffer、原生 DOM/BOM 对象等

  • 第三方库
    目前而言最完善的深拷贝方法是 Lodash 库的 _.cloneDeep(value) 方法

    🔗 Lodash中文文档 —— _.cloneDeep(value)

手写深拷贝

手写一个简易的深拷贝:

const deepCopy = (source) => {
  //判断是否为arr
  const isArray = (arr) =>
    Object.prototype.toString.call(arr) === "[object Array]";
  //判断是否为object和function
  const isObject = (obj) =>
    obj !== null && (typeof obj === "object" || typeof obj === "function");
  //递归拷贝
  const copy = (input) => {
    //如果是函数或者不是对象,直接返回(函数和非对象不需要深拷贝)
    if (typeof input === "function" || !isObject(input)) return input;
    const output = isArray(input) ? [] : {};
    for (let key in input) {
      //确保只复制对象自身的属性,而不是原型链上的属性
      if (input.hasOwnProperty(key)) {
        output[key] = copy(input[key]);
      }
    }
    return output;
  };
  return copy(source);
};

验证深拷贝结果:

const school2 = deepCopy(school);
school2.name = "buaa2";
console.log(school.name); // buaa
console.log(school2.name); // buaa2
const arr1 = [1, 2];
const arr2 = deepCopy(arr1);
arr2[0] = 3;
console.log(arr1[0]); // 1
console.log(arr2[0]); // 3

对于很多边界情况,以上简易版本还有许多地方需要处理,需要具体情况具体分析,但主要思想就是通过这样递归的方法,实现引用类型的深拷贝


http://www.kler.cn/news/324117.html

相关文章:

  • npm下载淘宝镜像的方式和用法
  • 在一个.NET Core项目中使用RabbitMQ进行即时消息管理
  • 前端注释规范
  • Mac安装manim
  • R包:VennDiagram韦恩图
  • 1.2.3 HuggingFists安装说明-MacOS安装
  • 供应链 | 顶刊POMS论文精读:交易成本经济学(TCE)——供应链效率理论
  • 小白投资理财 - 证券开户
  • WPF MVVM入门系列教程(一、MVVM模式介绍)
  • React 有哪些生命周期
  • 开源服务器管理软件Nexterm
  • 提取出来的ip与我原本的ip是在同一个区吗
  • Python编码系列—Python备忘录模式:掌握对象状态保存与恢复技术
  • 【成神之路】Ambari实战-013-代码生命周期-metainfo-configFiles详解
  • 【Linux】包管理器、vim详解及简单配置
  • 实战C++手写线程池
  • windows11下vscode配置lua环境
  • 1欧几里得聚类提取
  • WPF入门教学二十二 多线程与异步编程
  • Django——admin创建和使用
  • 【Python游戏开发】扫雷游戏demo
  • Linux云计算 |【第四阶段】RDBMS1-DAY2
  • 使用python获取百度一下,热搜TOP数据详情
  • 什么是聚类?
  • Docker数据卷有哪些常见的驱动类型?
  • K8S真正删除pod
  • SeeClick: Harnessing GUI Grounding for Advanced Visual GUI Agents论文学习
  • socket编程描述tcp的三次握手
  • Postman/Jmeter接口测试
  • MATLAB中的并行计算:提升性能的策略与实践