编辑
2023-11-11
JS基础
00
请注意,本文编写于 104 天前,最后修改于 104 天前,其中某些信息可能已经过时。

目录

什么是深拷贝和浅拷贝
为什么使用JSON方法实现深拷贝
JSON方法实现深拷贝存在的问题
如何解决JSON方法实现深拷贝存在的问题
总结

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

什么是深拷贝和浅拷贝

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

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

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

  • 使用递归方法
  • 使用第三方库方法
  • 使用特殊处理方法
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:CreatorRay

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!