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

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

什么是深拷贝和浅拷贝

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

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

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

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

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

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

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

js 复制代码
let clone = Object.assign({}, obj); // 或者 let clone = {...obj};

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

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

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

js 复制代码
let clone = JSON.parse(JSON.stringify(obj));

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

js 复制代码
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会报错,无法进行序列化。比如:
js 复制代码
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会丢失这些值,无法进行序列化。比如:
js 复制代码
let obj = {
  name: "Tom",
  age: undefined, // obj的age属性是undefined
  hobbies: ["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))会失真,无法还原为原来的类型。比如:
js 复制代码
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,无法还原为原来的类型。比如:
js 复制代码
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、正则表达式等类型的值
  • 不能处理构造函数生成的对象

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

  • 使用递归方法
  • 使用第三方库方法
  • 使用特殊处理方法
相关推荐
沉登c13 分钟前
Javascript客户端时间与服务器时间
服务器·javascript
持久的棒棒君16 分钟前
ElementUI 2.x 输入框回车后在调用接口进行远程搜索功能
前端·javascript·elementui
2401_8572979127 分钟前
秋招内推2025-招联金融
java·前端·算法·金融·求职招聘
bigcarp1 小时前
requests 中data=xxx、json=xxx、params=xxx 分别什么时候用
json
undefined&&懒洋洋1 小时前
Web和UE5像素流送、通信教程
前端·ue5
大前端爱好者3 小时前
React 19 新特性详解
前端
小程xy3 小时前
react 知识点汇总(非常全面)
前端·javascript·react.js
随云6323 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
随云6323 小时前
WebGL编程指南之进入三维世界
前端·webgl
J老熊4 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构