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、正则表达式等类型的值
  • 不能处理构造函数生成的对象

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

  • 使用递归方法
  • 使用第三方库方法
  • 使用特殊处理方法
相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端