JSON方法实现深拷贝存在的问题
  owiLjYy11txu 2023年12月06日 33 0


现在的前端面试中,深拷贝出现的频率极高。常规的问题中,可能首先问你,什么是深拷贝,实现深拷贝的方式都有哪些,你可能会答出几点,比如通过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属性是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))会失真,无法还原为原来的类型。比如:
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、正则表达式等类型的值
  • 不能处理构造函数生成的对象

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

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


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月06日 0

暂无评论

推荐阅读
  f18CFixvrKz8   2024年05月20日   85   0   0 JavaScript
  fxrR9b8fJ5Wh   2024年05月17日   47   0   0 JavaScript
  2xk0JyO908yA   2024年04月28日   37   0   0 JavaScript
owiLjYy11txu