现在的前端面试中,深拷贝出现的频率极高。常规的问题中,可能首先问你,什么是深拷贝,实现深拷贝的方式都有哪些,你可能会答出几点,比如通过JSON对象提供的JSON.strinfy和JSON.parse来实现,因为这种实现方式异常简单,一行代码即可,心里美滋滋,你让我手写我丝毫不慌。那么,面试官如果反手问一句,通过JSON提供的方法实现深拷贝会不会存在哪些问题?你是否能答出满意的结果呢。
对于不了解深拷贝的同学,我们首先介绍一下JavaScript
中深拷贝和浅拷贝的概念,再讨论实现方式以及其中存在的问题。
在JavaScript
中,我们经常需要对对象或数组进行复制,以便在不影响原数据的情况下进行操作。这时候,我们就需要区分深拷贝和浅拷贝的概念。
举个例子,假设我们有一个对象obj
,它的结构如下:
jslet obj = {
name: "Tom",
age: 18,
hobbies: ["basketball", "football"],
friend: {
name: "Jerry",
age: 17
}
};
如果我们使用浅拷贝的方法,比如Object.assign
或扩展运算符,来复制obj
,得到一个新的对象clone
,那么clone
的结构如下:
jslet clone = Object.assign({}, obj); // 或者 let clone = {...obj};
clone
的name
和age
属性是基本类型,所以复制的是真正的值,而hobbies
和friend
属性是引用类型,所以复制的是引用地址,指向原对象的属性。这样,如果我们修改clone
的hobbies
或friend
属性,就会影响到obj
的对应属性,比如:
jsclone.hobbies.push("tennis"); // 修改clone的hobbies属性
console.log(obj.hobbies); // ["basketball", "football", "tennis"],obj的hobbies属性也被修改了
如果我们使用深拷贝的方法,比如JSON.parse(JSON.stringify(obj))
,来复制obj
,得到一个新的对象clone
,那么clone
的结构如下:
jslet clone = JSON.parse(JSON.stringify(obj));
clone
的所有属性都是基本类型,或者是新创建的引用类型,与原对象没有任何关联。这样,如果我们修改clone
的任何属性,都不会影响到obj
的对应属性,比如:
jsclone.friend.name = "Bob"; // 修改clone的friend属性
console.log(obj.friend.name); // "Jerry",obj的friend属性没有被修改
使用JSON.parse(JSON.stringify(obj))
实现深拷贝,是一种非常简单而又有效的方法。
它的原理是利用JSON.stringify
将对象或数组序列化为一个JSON
字符串,然后利用JSON.parse
将字符串解析为一个新的对象或数组,从而实现深拷贝。
这种方法的优点是:
虽然使用JSON.parse(JSON.stringify(obj))
实现深拷贝很方便,但是它也有很多的局限性和问题,需要我们注意。这些问题主要包括:
JSON.stringify
会报错,无法进行序列化。比如:jslet 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
等类型的值,那么JSON.stringify
会丢失这些值,无法进行序列化。比如:jslet 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
、正则表达式等类型的值,那么JSON.parse(JSON.stringify(obj))
会失真,无法还原为原来的类型。比如:jslet 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
,无法还原为原来的类型。比如:jsfunction 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方法实现深拷贝存在的问题,我们可以采用以下几种解决方案:
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
、正则表达式等类型的值为了解决这些问题,我们可以采用以下几种解决方案:
本文作者:CreatorRay
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!