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

JSON方法实现深拷贝存在的问题

现在的前端面试中,深拷贝出现的频率极高。常规的问题中,可能首先问你,什么是深拷贝,实现深拷贝的方式都有哪些,你可能会答出几点,比如通过JSON对象提供的JSON.strinfy和JSON.parse来实现,因为这种实现方式异常简单,一行代码即可,心里美滋滋,你让我手写我丝毫不慌。那么,面试官如果反手问一句,通过JSON提供的方法实现深拷贝会不会存在哪些问题?你是否能答出满意的结果呢。

什么是深拷贝和浅拷贝

对于不了解深拷贝的同学,我们首先介绍一下JavaScript中深拷贝和浅拷贝的概念,再讨论实现方式以及其中存在的问题。

公众号:Code程序人生,个人网站:https://creatorblog.cn

JavaScript中,我们经常需要对对象或数组进行复制,以便在不影响原数据的情况下进行操作。这时候,我们就需要区分深拷贝和浅拷贝的概念。

  • 浅拷贝:只复制对象或数组的第一层属性,如果属性的值是引用类型,那么复制的是引用地址,而不是真正的值。这样,修改复制后的对象或数组,可能会影响到原对象或数组。
  • 深拷贝:完全复制对象或数组的所有层级属性,如果属性的值是引用类型,那么递归复制其内部的属性,直到所有的值都是基本类型。这样,修改复制后的对象或数组,不会影响到原对象或数组。

举个例子,假设我们有一个对象obj,它的结构如下:

let obj = {name: "Tom",age: 18,hobbies: ["basketball", "football"],friend: {name: "Jerry",age: 17}
};

如果我们使用浅拷贝的方法,比如Object.assign或扩展运算符,来复制obj,得到一个新的对象clone,那么clone的结构如下:

let clone = Object.assign({}, obj); // 或者 let clone = {...obj};

clonenameage属性是基本类型,所以复制的是真正的值,而hobbiesfriend属性是引用类型,所以复制的是引用地址,指向原对象的属性。这样,如果我们修改clonehobbiesfriend属性,就会影响到obj的对应属性,比如:

clone.hobbies.push("tennis"); // 修改clone的hobbies属性
console.log(obj.hobbies); // ["basketball", "football", "tennis"],obj的hobbies属性也被修改了

如果我们使用深拷贝的方法,比如JSON.parse(JSON.stringify(obj)),来复制obj,得到一个新的对象clone,那么clone的结构如下:

let clone = JSON.parse(JSON.stringify(obj));

clone的所有属性都是基本类型,或者是新创建的引用类型,与原对象没有任何关联。这样,如果我们修改clone的任何属性,都不会影响到obj的对应属性,比如:

clone.friend.name = "Bob"; // 修改clone的friend属性
console.log(obj.friend.name); // "Jerry",obj的friend属性没有被修改

为什么使用JSON方法实现深拷贝

使用JSON.parse(JSON.stringify(obj))实现深拷贝,是一种非常简单而又有效的方法。

它的原理是利用JSON.stringify将对象或数组序列化为一个JSON字符串,然后利用JSON.parse将字符串解析为一个新的对象或数组,从而实现深拷贝。

这种方法的优点是:

  • 代码简洁,一行就能搞定
  • 不需要考虑对象或数组的层级结构,可以自动处理嵌套的情况
  • 不需要考虑对象或数组的属性名,可以自动复制所有的属性

JSON方法实现深拷贝存在的问题

虽然使用JSON.parse(JSON.stringify(obj))实现深拷贝很方便,但是它也有很多的局限性和问题,需要我们注意。这些问题主要包括:

  • 不能处理循环引用的情况:如果对象或数组中存在循环引用的情况,即一个属性的值是对象或数组本身,或者是对象或数组的某个祖先属性,那么JSON.stringify会报错,无法进行序列化。比如:
let obj = {name: "Tom",age: 18,hobbies: ["basketball", "football"],friend: {name: "Jerry",age: 17}
};
obj.self = obj; // obj的self属性指向obj本身,形成循环引用
let clone = JSON.parse(JSON.stringify(obj)); // TypeError: Converting circular structure to JSON
  • 不能处理undefined、Symbol等类型的值:如果对象或数组中存在undefinedSymbol等类型的值,那么JSON.stringify会丢失这些值,无法进行序列化。比如:
let obj = {name: "Tom",age: undefined, // obj的age属性是undefinedhobbies: ["basketball", "football"],friend: {name: "Jerry",age: 17},[Symbol("id")]: 123 // obj的Symbol属性
};
let clone = JSON.parse(JSON.stringify(obj));
console.log(clone); // {name: "Tom", hobbies: ["basketball", "football"], friend: {name: "Jerry", age: 17}},clone的age属性和Symbol属性丢失了
  • 不能处理Date、正则表达式等类型的值:如果对象或数组中存在Date、正则表达式等类型的值,那么JSON.parse(JSON.stringify(obj))会失真,无法还原为原来的类型。比如:
let obj = {name: "Tom",age: 18,hobbies: ["basketball", "football"],friend: {name: "Jerry",age: 17},birthday: new Date("2000-01-01"), // obj的birthday属性是Date类型pattern: /\w+/ // obj的pattern属性是正则表达式类型
};
let clone = JSON.parse(JSON.stringify(obj));
console.log(clone.birthday); // "2000-01-01T00:00:00.000Z",clone的birthday属性变成了字符串
console.log(clone.pattern); // {},clone的pattern属性变成了空对象
  • 不能处理构造函数生成的对象:如果对象是由构造函数生成的,那么JSON.parse(JSON.stringify(obj))会丢弃对象的constructor,无法还原为原来的类型。比如:
function Person(name, age) {this.name = name;this.age = age;
}
let obj = new Person("Tom", 18); // obj是由Person构造函数生成的
let clone = JSON.parse(JSON.stringify(obj));
console.log(clone.constructor); // [Function: Object],clone的constructor变成了Object
console.log(clone instanceof Person); // false,clone不是Person的实例

如何解决JSON方法实现深拷贝存在的问题

针对JSON方法实现深拷贝存在的问题,我们可以采用以下几种解决方案:

  • 使用递归方法:使用递归遍历对象或数组的每个属性,判断属性的类型,如果是基本类型,直接复制,如果是引用类型,创建一个新的对象或数组,继续递归拷贝,这种方法可以处理循环引用的情况,但是需要注意栈溢出的风险。
  • 使用第三方库方法:使用一些成熟的第三方库,如lodashjQuery等,它们提供了一些深拷贝的函数,可以处理各种类型的值,但是也有一些性能或兼容性的问题。
  • 使用特殊处理方法:针对一些特殊的类型,如Date、正则表达式、构造函数等,我们可以使用一些特殊的处理方法,来保证深拷贝的正确性。比如,对于Date类型,我们可以使用new Date(obj.getTime())来复制一个新的Date对象,对于正则表达式类型,我们可以使用new RegExp(obj.source, obj.flags)来复制一个新的正则表达式对象,对于构造函数类型,我们可以使用new obj.constructor()来复制一个新的构造函数对象。

总结

使用JSON.parse(JSON.stringify(obj))实现深拷贝,是一种简单而又有效的方法,但是也有很多的局限性和问题,需要我们注意。这些问题主要包括:

  • 不能处理循环引用的情况
  • 不能处理undefinedSymbol等类型的值
  • 不能处理Date、正则表达式等类型的值
  • 不能处理构造函数生成的对象

为了解决这些问题,我们可以采用以下几种解决方案:

  • 使用递归方法
  • 使用第三方库方法
  • 使用特殊处理方法

相关文章:

  • 2022年12月 Python(四级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • JavaScript中的Math
  • Ionic组件 ion-list ion-list-header
  • Python中的Socket编程
  • scrapy发json的post请求
  • 双十一“静悄悄”?VR购物拉满沉浸式购物体验
  • C# OpenCvSharp 基于直线检测的文本图像倾斜校正
  • Python爬虫——入门爬取网页数据
  • Wix使用velo添加Google ads tag并在form表单提交时向谷歌发送事件
  • AI由许多不同的技术组成,其中一些最核心的技术如下
  • 【手动创建UIWindow Objective-C语言】
  • 使用 pubsub-js 进行消息发布订阅
  • Django——orm模块创建表关系
  • 键盘win键无法使用,win+r不生效、win键没反应、Windows键失灵解决方案(亲测可以解决)
  • 【狂神说Java】Dubbo + Zookeeper
  • es6要点
  • exif信息对照
  • JavaScript设计模式之工厂模式
  • JS 面试题总结
  • mac修复ab及siege安装
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • SwizzleMethod 黑魔法
  • VUE es6技巧写法(持续更新中~~~)
  • Vue.js源码(2):初探List Rendering
  • win10下安装mysql5.7
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 小而合理的前端理论:rscss和rsjs
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​TypeScript都不会用,也敢说会前端?
  • (3)(3.5) 遥测无线电区域条例
  • (Oracle)SQL优化技巧(一):分页查询
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (九)信息融合方式简介
  • (四) 虚拟摄像头vivi体验
  • (转)拼包函数及网络封包的异常处理(含代码)
  • (转)视频码率,帧率和分辨率的联系与区别
  • .NET 4.0中的泛型协变和反变
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .NET Standard 的管理策略
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .NET实现之(自动更新)
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • .php文件都打不开,打不开php文件怎么办
  • .stream().map与.stream().flatMap的使用
  • /bin/bash^M: bad interpreter: No such file ordirectory
  • /etc/skel 目录作用
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [ai笔记9] openAI Sora技术文档引用文献汇总