JSON方法实现深拷贝存在的问题-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

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

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

  • 使用递归方法
  • 使用第三方库方法
  • 使用特殊处理方法
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6