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};
clone
的name
和age
属性是基本类型所以复制的是真正的值而hobbies
和friend
属性是引用类型所以复制的是引用地址指向原对象的属性。这样如果我们修改clone
的hobbies
或friend
属性就会影响到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等类型的值如果对象或数组中存在
undefined
、Symbol
等类型的值那么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
方法实现深拷贝存在的问题我们可以采用以下几种解决方案
- 使用递归方法使用递归遍历对象或数组的每个属性判断属性的类型如果是基本类型直接复制如果是引用类型创建一个新的对象或数组继续递归拷贝这种方法可以处理循环引用的情况但是需要注意栈溢出的风险。
- 使用第三方库方法使用一些成熟的第三方库如
lodash
、jQuery
等它们提供了一些深拷贝的函数可以处理各种类型的值但是也有一些性能或兼容性的问题。 - 使用特殊处理方法针对一些特殊的类型如
Date
、正则表达式、构造函数等我们可以使用一些特殊的处理方法来保证深拷贝的正确性。比如对于Date
类型我们可以使用new Date(obj.getTime())
来复制一个新的Date
对象对于正则表达式类型我们可以使用new RegExp(obj.source, obj.flags)
来复制一个新的正则表达式对象对于构造函数类型我们可以使用new obj.constructor()
来复制一个新的构造函数对象。
总结
使用JSON.parse(JSON.stringify(obj))
实现深拷贝是一种简单而又有效的方法但是也有很多的局限性和问题需要我们注意。这些问题主要包括
- 不能处理循环引用的情况
- 不能处理
undefined
、Symbol
等类型的值 - 不能处理
Date
、正则表达式等类型的值 - 不能处理构造函数生成的对象
为了解决这些问题我们可以采用以下几种解决方案
- 使用递归方法
- 使用第三方库方法
- 使用特殊处理方法
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |